Browse Source

Merge pull request #2180 from SixLabors/js/decoder-options

Introduce Shared General Decoder Options plus Specialization
pull/2230/head
James Jackson-South 4 years ago
committed by GitHub
parent
commit
74e2b7cf16
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 22
      src/ImageSharp/Advanced/AotCompilerTools.cs
  2. 47
      src/ImageSharp/Formats/Bmp/BmpDecoder.cs
  3. 42
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  4. 20
      src/ImageSharp/Formats/Bmp/BmpDecoderOptions.cs
  5. 16
      src/ImageSharp/Formats/Bmp/IBmpDecoderOptions.cs
  6. 49
      src/ImageSharp/Formats/DecoderOptions.cs
  7. 41
      src/ImageSharp/Formats/Gif/GifDecoder.cs
  8. 72
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  9. 23
      src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs
  10. 20
      src/ImageSharp/Formats/IImageDecoder.cs
  11. 8
      src/ImageSharp/Formats/IImageDecoderInternals.cs
  12. 45
      src/ImageSharp/Formats/IImageDecoderSpecialized{T}.cs
  13. 10
      src/ImageSharp/Formats/IImageInfoDetector.cs
  14. 16
      src/ImageSharp/Formats/ISpecializedDecoderOptions.cs
  15. 182
      src/ImageSharp/Formats/ImageDecoderExtensions.cs
  16. 49
      src/ImageSharp/Formats/ImageDecoderUtilities.cs
  17. 16
      src/ImageSharp/Formats/Jpeg/IJpegDecoderOptions.cs
  18. 64
      src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
  19. 94
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  20. 19
      src/ImageSharp/Formats/Jpeg/JpegDecoderOptions.cs
  21. 29
      src/ImageSharp/Formats/Jpeg/JpegDecoderResizeMode.cs
  22. 28
      src/ImageSharp/Formats/Pbm/PbmDecoder.cs
  23. 103
      src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs
  24. 16
      src/ImageSharp/Formats/Png/IPngDecoderOptions.cs
  25. 72
      src/ImageSharp/Formats/Png/PngDecoder.cs
  26. 56
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  27. 12
      src/ImageSharp/Formats/Tga/ITgaDecoderOptions.cs
  28. 27
      src/ImageSharp/Formats/Tga/TgaDecoder.cs
  29. 37
      src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
  30. 3
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs
  31. 33
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs
  32. 3
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs
  33. 3
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs
  34. 3
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs
  35. 3
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs
  36. 3
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs
  37. 3
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6TiffCompression.cs
  38. 4
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffJpegSpectralConverter{TPixel}.cs
  39. 13
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/WebpTiffCompression.cs
  40. 9
      src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs
  41. 6
      src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs
  42. 23
      src/ImageSharp/Formats/Tiff/ITiffDecoderOptions.cs
  43. 39
      src/ImageSharp/Formats/Tiff/TiffDecoder.cs
  44. 56
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  45. 23
      src/ImageSharp/Formats/Webp/IWebpDecoderOptions.cs
  46. 39
      src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs
  47. 51
      src/ImageSharp/Formats/Webp/WebpDecoder.cs
  48. 108
      src/ImageSharp/Formats/Webp/WebpDecoderCore.cs
  49. 38
      src/ImageSharp/Image.Decode.cs
  50. 451
      src/ImageSharp/Image.FromBytes.cs
  51. 295
      src/ImageSharp/Image.FromFile.cs
  52. 407
      src/ImageSharp/Image.FromStream.cs
  53. 68
      src/ImageSharp/Image.LoadPixelData.cs
  54. 4
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs
  55. 4
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs
  56. 21
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_Aggregate.cs
  57. 23
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs
  58. 5
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs
  59. 5
      tests/ImageSharp.Benchmarks/Codecs/Tga/DecodeTga.cs
  60. 4
      tests/ImageSharp.Benchmarks/Codecs/Webp/DecodeWebp.cs
  61. 6
      tests/ImageSharp.Benchmarks/General/Buffer2D_DangerousGetRowSpan.cs
  62. 35
      tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs
  63. 344
      tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
  64. 194
      tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs
  65. 136
      tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs
  66. 103
      tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs
  67. 154
      tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs
  68. 154
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs
  69. 211
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
  70. 6
      tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs
  71. 31
      tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs
  72. 11
      tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs
  73. 25
      tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs
  74. 10
      tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs
  75. 24
      tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
  76. 225
      tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
  77. 282
      tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs
  78. 109
      tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs
  79. 25
      tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs
  80. 2
      tests/ImageSharp.Tests/Formats/Tga/TgaFileHeaderTests.cs
  81. 12
      tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs
  82. 6
      tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs
  83. 2
      tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs
  84. 2
      tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs
  85. 31
      tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
  86. 202
      tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs
  87. 4
      tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs
  88. 255
      tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs
  89. 61
      tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs
  90. 91
      tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs
  91. 60
      tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs
  92. 105
      tests/ImageSharp.Tests/Image/ImageTests.Identify.cs
  93. 29
      tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs
  94. 67
      tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_PassLocalConfiguration.cs
  95. 66
      tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_UseDefaultConfiguration.cs
  96. 90
      tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_PassLocalConfiguration.cs
  97. 83
      tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_UseGlobalConfiguration.cs
  98. 63
      tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_PassLocalConfiguration.cs
  99. 17
      tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_ThrowsRightException.cs
  100. 91
      tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_UseDefaultConfiguration.cs

22
src/ImageSharp/Advanced/AotCompilerTools.cs

@ -178,7 +178,7 @@ namespace SixLabors.ImageSharp.Advanced
img.CloneAs<Short2>(default);
img.CloneAs<Short4>(default);
ImageFrame.LoadPixelData<TPixel>(default, default(ReadOnlySpan<TPixel>), default, default);
ImageFrame.LoadPixelData(default, default(ReadOnlySpan<TPixel>), default, default);
ImageFrame.LoadPixelData<TPixel>(default, default(ReadOnlySpan<byte>), default, default);
}
@ -217,14 +217,14 @@ namespace SixLabors.ImageSharp.Advanced
private static void AotCompileImageDecoderInternals<TPixel>()
where TPixel : unmanaged, IPixel<TPixel>
{
default(WebpDecoderCore).Decode<TPixel>(default, default, default);
default(BmpDecoderCore).Decode<TPixel>(default, default, default);
default(GifDecoderCore).Decode<TPixel>(default, default, default);
default(JpegDecoderCore).Decode<TPixel>(default, default, default);
default(PbmDecoderCore).Decode<TPixel>(default, default, default);
default(PngDecoderCore).Decode<TPixel>(default, default, default);
default(TgaDecoderCore).Decode<TPixel>(default, default, default);
default(TiffDecoderCore).Decode<TPixel>(default, default, default);
default(WebpDecoderCore).Decode<TPixel>(default, default);
default(BmpDecoderCore).Decode<TPixel>(default, default);
default(GifDecoderCore).Decode<TPixel>(default, default);
default(JpegDecoderCore).Decode<TPixel>(default, default);
default(PbmDecoderCore).Decode<TPixel>(default, default);
default(PngDecoderCore).Decode<TPixel>(default, default);
default(TgaDecoderCore).Decode<TPixel>(default, default);
default(TiffDecoderCore).Decode<TPixel>(default, default);
}
/// <summary>
@ -286,9 +286,7 @@ namespace SixLabors.ImageSharp.Advanced
private static void AotCompileImageDecoder<TPixel, TDecoder>()
where TPixel : unmanaged, IPixel<TPixel>
where TDecoder : class, IImageDecoder
{
default(TDecoder).Decode<TPixel>(default, default, default);
}
=> default(TDecoder).Decode<TPixel>(default, default, default);
/// <summary>
/// This method pre-seeds the all <see cref="IImageProcessor" /> in the AoT compiler.

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

@ -10,43 +10,40 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <summary>
/// Image decoder for generating an image out of a Windows bitmap stream.
/// </summary>
/// <remarks>
/// Does not support the following formats at the moment:
/// <list type="bullet">
/// <item>JPG</item>
/// <item>PNG</item>
/// <item>Some OS/2 specific subtypes like: Bitmap Array, Color Icon, Color Pointer, Icon, Pointer.</item>
/// </list>
/// Formats will be supported in a later releases. We advise always
/// to use only 24 Bit Windows bitmaps.
/// </remarks>
public sealed class BmpDecoder : IImageDecoder, IBmpDecoderOptions, IImageInfoDetector
public class BmpDecoder : IImageDecoderSpecialized<BmpDecoderOptions>
{
/// <summary>
/// Gets or sets a value indicating how to deal with skipped pixels, which can occur during decoding run length encoded bitmaps.
/// </summary>
public RleSkippedPixelHandling RleSkippedPixelHandling { get; set; } = RleSkippedPixelHandling.Black;
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));
var decoder = new BmpDecoderCore(configuration, this);
return decoder.Decode<TPixel>(configuration, stream, cancellationToken);
return new BmpDecoderCore(new() { GeneralOptions = options }).Identify(options.Configuration, stream, cancellationToken);
}
/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>(configuration, stream, cancellationToken);
/// <inheritdoc/>
Image<TPixel> IImageDecoder.Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> ((IImageDecoderSpecialized<BmpDecoderOptions>)this).Decode<TPixel>(new() { GeneralOptions = options }, stream, cancellationToken);
/// <inheritdoc/>
Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> ((IImageDecoderSpecialized<BmpDecoderOptions>)this).Decode(new() { GeneralOptions = options }, stream, cancellationToken);
/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken)
Image<TPixel> IImageDecoderSpecialized<BmpDecoderOptions>.Decode<TPixel>(BmpDecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));
return new BmpDecoderCore(configuration, this).Identify(configuration, stream, cancellationToken);
Image<TPixel> image = new BmpDecoderCore(options).Decode<TPixel>(options.GeneralOptions.Configuration, stream, cancellationToken);
ImageDecoderUtilities.Resize(options.GeneralOptions, image);
return image;
}
/// <inheritdoc/>
Image IImageDecoderSpecialized<BmpDecoderOptions>.Decode(BmpDecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> ((IImageDecoderSpecialized<BmpDecoderOptions>)this).Decode<Rgba32>(options, stream, cancellationToken);
}
}

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

@ -89,34 +89,38 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// </summary>
private BmpInfoHeader infoHeader;
/// <summary>
/// The global configuration.
/// </summary>
private readonly Configuration configuration;
/// <summary>
/// Used for allocating memory during processing operations.
/// </summary>
private readonly MemoryAllocator memoryAllocator;
/// <summary>
/// The bitmap decoder options.
/// How to deal with skipped pixels,
/// which can occur during decoding run length encoded bitmaps.
/// </summary>
private readonly IBmpDecoderOptions options;
private readonly RleSkippedPixelHandling rleSkippedPixelHandling;
/// <summary>
/// Initializes a new instance of the <see cref="BmpDecoderCore"/> class.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="options">The options.</param>
public BmpDecoderCore(Configuration configuration, IBmpDecoderOptions options)
public BmpDecoderCore(BmpDecoderOptions options)
{
this.Configuration = configuration;
this.memoryAllocator = configuration.MemoryAllocator;
this.options = options;
this.Options = options.GeneralOptions;
this.rleSkippedPixelHandling = options.RleSkippedPixelHandling;
this.configuration = options.GeneralOptions.Configuration;
this.memoryAllocator = this.configuration.MemoryAllocator;
}
/// <inheritdoc />
public Configuration Configuration { get; }
public DecoderOptions Options { get; }
/// <summary>
/// Gets the dimensions of the image.
/// </summary>
/// <inheritdoc />
public Size Dimensions => new(this.infoHeader.Width, this.infoHeader.Height);
/// <inheritdoc />
@ -128,7 +132,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{
int bytesPerColorMapEntry = this.ReadImageHeaders(stream, out bool inverted, out byte[] palette);
image = new Image<TPixel>(this.Configuration, this.infoHeader.Width, this.infoHeader.Height, this.metadata);
image = new Image<TPixel>(this.configuration, this.infoHeader.Width, this.infoHeader.Height, this.metadata);
Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer();
@ -325,7 +329,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
byte colorIdx = bufferRow[x];
if (undefinedPixelsSpan[rowStartIdx + x])
{
switch (this.options.RleSkippedPixelHandling)
switch (this.rleSkippedPixelHandling)
{
case RleSkippedPixelHandling.FirstColorOfPalette:
color.FromBgr24(Unsafe.As<byte, Bgr24>(ref colors[colorIdx * 4]));
@ -397,7 +401,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
int idx = rowStartIdx + (x * 3);
if (undefinedPixelsSpan[yMulWidth + x])
{
switch (this.options.RleSkippedPixelHandling)
switch (this.rleSkippedPixelHandling)
{
case RleSkippedPixelHandling.FirstColorOfPalette:
color.FromBgr24(Unsafe.As<byte, Bgr24>(ref bufferSpan[idx]));
@ -943,7 +947,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
int newY = Invert(y, height, inverted);
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(newY);
PixelOperations<TPixel>.Instance.FromBgr24Bytes(
this.Configuration,
this.configuration,
rowSpan,
pixelSpan,
width);
@ -971,7 +975,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
int newY = Invert(y, height, inverted);
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(newY);
PixelOperations<TPixel>.Instance.FromBgra32Bytes(
this.Configuration,
this.configuration,
rowSpan,
pixelSpan,
width);
@ -1006,7 +1010,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
this.stream.Read(rowSpan);
PixelOperations<Bgra32>.Instance.FromBgra32Bytes(
this.Configuration,
this.configuration,
rowSpan,
bgraRowSpan,
width);
@ -1042,7 +1046,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(newY);
PixelOperations<TPixel>.Instance.FromBgra32Bytes(
this.Configuration,
this.configuration,
rowSpan,
pixelSpan,
width);
@ -1056,7 +1060,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{
this.stream.Read(rowSpan);
PixelOperations<Bgra32>.Instance.FromBgra32Bytes(
this.Configuration,
this.configuration,
rowSpan,
bgraRowSpan,
width);

20
src/ImageSharp/Formats/Bmp/BmpDecoderOptions.cs

@ -0,0 +1,20 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Bmp
{
/// <summary>
/// Configuration options for decoding Windows Bitmap images.
/// </summary>
public sealed class BmpDecoderOptions : ISpecializedDecoderOptions
{
/// <inheritdoc/>
public DecoderOptions GeneralOptions { get; set; } = new();
/// <summary>
/// Gets or sets the value indicating how to deal with skipped pixels,
/// which can occur during decoding run length encoded bitmaps.
/// </summary>
public RleSkippedPixelHandling RleSkippedPixelHandling { get; set; }
}
}

16
src/ImageSharp/Formats/Bmp/IBmpDecoderOptions.cs

@ -1,16 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Bmp
{
/// <summary>
/// Image decoder options for decoding Windows bitmap streams.
/// </summary>
internal interface IBmpDecoderOptions
{
/// <summary>
/// Gets the value indicating how to deal with skipped pixels, which can occur during decoding run length encoded bitmaps.
/// </summary>
RleSkippedPixelHandling RleSkippedPixelHandling { get; }
}
}

49
src/ImageSharp/Formats/DecoderOptions.cs

@ -0,0 +1,49 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Transforms;
namespace SixLabors.ImageSharp.Formats
{
/// <summary>
/// Provides general configuration options for decoding image formats.
/// </summary>
public sealed class DecoderOptions
{
private static readonly Lazy<DecoderOptions> LazyOptions = new(() => new());
private uint maxFrames = int.MaxValue;
/// <summary>
/// Gets the shared default general decoder options instance.
/// </summary>
internal static DecoderOptions Default { get; } = LazyOptions.Value;
/// <summary>
/// Gets or sets a custom Configuration instance to be used by the image processing pipeline.
/// </summary>
public Configuration Configuration { get; set; } = Configuration.Default;
/// <summary>
/// Gets or sets the target size to decode the image into.
/// </summary>
public Size? TargetSize { get; set; } = null;
/// <summary>
/// Gets or sets the sampler to use when resizing during decoding.
/// </summary>
public IResampler Sampler { get; set; } = KnownResamplers.Box;
/// <summary>
/// Gets or sets a value indicating whether to ignore encoded metadata when decoding.
/// </summary>
public bool SkipMetadata { get; set; } = false;
/// <summary>
/// Gets or sets the maximum number of image frames to decode, inclusive.
/// </summary>
public uint MaxFrames { get => this.maxFrames; set => this.maxFrames = Math.Clamp(value, 1, int.MaxValue); }
}
}

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

@ -3,7 +3,6 @@
using System.IO;
using System.Threading;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Gif
@ -11,37 +10,33 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary>
/// Decoder for generating an image out of a gif encoded stream.
/// </summary>
public sealed class GifDecoder : IImageDecoder, IGifDecoderOptions, IImageInfoDetector
public sealed class GifDecoder : IImageDecoder
{
/// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
public bool IgnoreMetadata { get; set; } = false;
/// <summary>
/// Gets or sets the decoding mode for multi-frame images
/// </summary>
public FrameDecodingMode DecodingMode { get; set; } = FrameDecodingMode.All;
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
var decoder = new GifDecoderCore(configuration, this);
return decoder.Decode<TPixel>(configuration, stream, cancellationToken);
}
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));
/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>(configuration, stream, cancellationToken);
return new GifDecoderCore(options).Identify(options.Configuration, stream, cancellationToken);
}
/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken)
Image<TPixel> IImageDecoder.Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));
var decoder = new GifDecoderCore(configuration, this);
return decoder.Identify(configuration, stream, cancellationToken);
GifDecoderCore decoder = new(options);
Image<TPixel> image = decoder.Decode<TPixel>(options.Configuration, stream, cancellationToken);
ImageDecoderUtilities.Resize(options, image);
return image;
}
/// <inheritdoc/>
Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> ((IImageDecoder)this).Decode<Rgba32>(options, stream, cancellationToken);
}
}

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

@ -56,6 +56,26 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary>
private GifImageDescriptor imageDescriptor;
/// <summary>
/// The global configuration.
/// </summary>
private readonly Configuration configuration;
/// <summary>
/// Used for allocating memory during processing operations.
/// </summary>
private readonly MemoryAllocator memoryAllocator;
/// <summary>
/// The maximum number of frames to decode. Inclusive.
/// </summary>
private readonly uint maxFrames;
/// <summary>
/// Whether to skip metadata during decode.
/// </summary>
private readonly bool skipMetadata;
/// <summary>
/// The abstract metadata.
/// </summary>
@ -69,39 +89,27 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary>
/// Initializes a new instance of the <see cref="GifDecoderCore"/> class.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="options">The decoder options.</param>
public GifDecoderCore(Configuration configuration, IGifDecoderOptions options)
public GifDecoderCore(DecoderOptions options)
{
this.IgnoreMetadata = options.IgnoreMetadata;
this.DecodingMode = options.DecodingMode;
this.Configuration = configuration ?? Configuration.Default;
this.Options = options;
this.configuration = options.Configuration;
this.skipMetadata = options.SkipMetadata;
this.maxFrames = options.MaxFrames;
this.memoryAllocator = this.configuration.MemoryAllocator;
}
/// <inheritdoc />
public Configuration Configuration { get; }
/// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
public bool IgnoreMetadata { get; internal set; }
public DecoderOptions Options { get; }
/// <summary>
/// Gets the decoding mode for multi-frame images.
/// </summary>
public FrameDecodingMode DecodingMode { get; }
/// <summary>
/// Gets the dimensions of the image.
/// </summary>
/// <inheritdoc />
public Size Dimensions => new(this.imageDescriptor.Width, this.imageDescriptor.Height);
private MemoryAllocator MemoryAllocator => this.Configuration.MemoryAllocator;
/// <inheritdoc />
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
uint frameCount = 0;
Image<TPixel> image = null;
ImageFrame<TPixel> previousFrame = null;
try
@ -114,7 +122,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
{
if (nextFlag == GifConstants.ImageLabel)
{
if (previousFrame != null && this.DecodingMode == FrameDecodingMode.First)
if (previousFrame != null && ++frameCount == this.maxFrames)
{
break;
}
@ -277,9 +285,9 @@ namespace SixLabors.ImageSharp.Formats.Gif
this.stream.Read(this.buffer, 0, GifConstants.ApplicationBlockSize);
bool isXmp = this.buffer.AsSpan().StartsWith(GifConstants.XmpApplicationIdentificationBytes);
if (isXmp && !this.IgnoreMetadata)
if (isXmp && !this.skipMetadata)
{
var extension = GifXmpApplicationExtension.Read(this.stream, this.MemoryAllocator);
var extension = GifXmpApplicationExtension.Read(this.stream, this.memoryAllocator);
if (extension.Data.Length > 0)
{
this.metadata.XmpProfile = new XmpProfile(extension.Data);
@ -346,13 +354,13 @@ namespace SixLabors.ImageSharp.Formats.Gif
GifThrowHelper.ThrowInvalidImageContentException($"Gif comment length '{length}' exceeds max '{GifConstants.MaxCommentSubBlockLength}' of a comment data block");
}
if (this.IgnoreMetadata)
if (this.skipMetadata)
{
this.stream.Seek(length, SeekOrigin.Current);
continue;
}
using IMemoryOwner<byte> commentsBuffer = this.MemoryAllocator.Allocate<byte>(length);
using IMemoryOwner<byte> commentsBuffer = this.memoryAllocator.Allocate<byte>(length);
Span<byte> commentsSpan = commentsBuffer.GetSpan();
this.stream.Read(commentsSpan);
@ -385,11 +393,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
if (this.imageDescriptor.LocalColorTableFlag)
{
int length = this.imageDescriptor.LocalColorTableSize * 3;
localColorTable = this.Configuration.MemoryAllocator.Allocate<byte>(length, AllocationOptions.Clean);
localColorTable = this.configuration.MemoryAllocator.Allocate<byte>(length, AllocationOptions.Clean);
this.stream.Read(localColorTable.GetSpan());
}
indices = this.Configuration.MemoryAllocator.Allocate2D<byte>(this.imageDescriptor.Width, this.imageDescriptor.Height, AllocationOptions.Clean);
indices = this.configuration.MemoryAllocator.Allocate2D<byte>(this.imageDescriptor.Width, this.imageDescriptor.Height, AllocationOptions.Clean);
this.ReadFrameIndices(indices);
Span<byte> rawColorTable = default;
@ -423,7 +431,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
private void ReadFrameIndices(Buffer2D<byte> indices)
{
int minCodeSize = this.stream.ReadByte();
using var lzwDecoder = new LzwDecoder(this.Configuration.MemoryAllocator, this.stream);
using var lzwDecoder = new LzwDecoder(this.configuration.MemoryAllocator, this.stream);
lzwDecoder.DecodePixels(minCodeSize, indices);
}
@ -451,12 +459,12 @@ namespace SixLabors.ImageSharp.Formats.Gif
{
if (!transFlag)
{
image = new Image<TPixel>(this.Configuration, imageWidth, imageHeight, Color.Black.ToPixel<TPixel>(), this.metadata);
image = new Image<TPixel>(this.configuration, imageWidth, imageHeight, Color.Black.ToPixel<TPixel>(), this.metadata);
}
else
{
// This initializes the image to become fully transparent because the alpha channel is zero.
image = new Image<TPixel>(this.Configuration, imageWidth, imageHeight, this.metadata);
image = new Image<TPixel>(this.configuration, imageWidth, imageHeight, this.metadata);
}
this.SetFrameMetadata(image.Frames.RootFrame.Metadata);
@ -675,7 +683,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
if (globalColorTableLength > 0)
{
this.globalColorTable = this.MemoryAllocator.Allocate<byte>(globalColorTableLength, AllocationOptions.Clean);
this.globalColorTable = this.memoryAllocator.Allocate<byte>(globalColorTableLength, AllocationOptions.Clean);
// Read the global color table data from the stream
stream.Read(this.globalColorTable.GetSpan());

23
src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs

@ -1,23 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Metadata;
namespace SixLabors.ImageSharp.Formats.Gif
{
/// <summary>
/// Decoder for generating an image out of a gif encoded stream.
/// </summary>
internal interface IGifDecoderOptions
{
/// <summary>
/// Gets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
bool IgnoreMetadata { get; }
/// <summary>
/// Gets the decoding mode for multi-frame images.
/// </summary>
FrameDecodingMode DecodingMode { get; }
}
}

20
src/ImageSharp/Formats/IImageDecoder.cs

@ -10,28 +10,34 @@ namespace SixLabors.ImageSharp.Formats
/// <summary>
/// Encapsulates properties and methods required for decoding an image from a stream.
/// </summary>
public interface IImageDecoder
public interface IImageDecoder : IImageInfoDetector
{
/// <summary>
/// Decodes the image from the specified stream to an <see cref="Image{TPixel}"/> of a specific pixel type.
/// </summary>
/// <remarks>
/// This method is designed to support the ImageSharp internal infrastructure and is not recommended for direct use.
/// </remarks>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="configuration">The configuration for the image.</param>
/// <param name="options">The general decoder options.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
// TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110)
Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>;
/// <summary>
/// Decodes the image from the specified stream to an <see cref="Image"/>.
/// </summary>
/// <param name="configuration">The configuration for the image.</param>
/// <remarks>
/// This method is designed to support the ImageSharp internal infrastructure and is not recommended for direct use.
/// </remarks>
/// <param name="options">The general decoder options.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>The <see cref="Image"/>.</returns>
// TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110)
Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken);
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken);
}
}

8
src/ImageSharp/Formats/IImageDecoderInternals.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
@ -9,14 +9,14 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats
{
/// <summary>
/// Abstraction for shared internals for ***DecoderCore implementations to be used with <see cref="ImageDecoderUtilities"/>.
/// Abstraction for shared internals for XXXDecoderCore implementations to be used with <see cref="ImageDecoderUtilities"/>.
/// </summary>
internal interface IImageDecoderInternals
{
/// <summary>
/// Gets the associated configuration.
/// Gets the general decoder options.
/// </summary>
Configuration Configuration { get; }
DecoderOptions Options { get; }
/// <summary>
/// Gets the dimensions of the image being decoded.

45
src/ImageSharp/Formats/IImageDecoderSpecialized{T}.cs

@ -0,0 +1,45 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.IO;
using System.Threading;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats
{
/// <summary>
/// The base class for all specialized image decoders.
/// </summary>
/// <typeparam name="T">The type of specialized options.</typeparam>
public interface IImageDecoderSpecialized<T> : IImageDecoder
where T : ISpecializedDecoderOptions
{
/// <summary>
/// Decodes the image from the specified stream to an <see cref="Image{TPixel}"/> of a specific pixel type.
/// </summary>
/// <remarks>
/// This method is designed to support the ImageSharp internal infrastructure and is not recommended for direct use.
/// </remarks>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="options">The specialized decoder options.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
public Image<TPixel> Decode<TPixel>(T options, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>;
/// <summary>
/// Decodes the image from the specified stream to an <see cref="Image"/> of a specific pixel type.
/// </summary>
/// <remarks>
/// This method is designed to support the ImageSharp internal infrastructure and is not recommended for direct use.
/// </remarks>
/// <param name="options">The specialized decoder options.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
public Image Decode(T options, Stream stream, CancellationToken cancellationToken);
}
}

10
src/ImageSharp/Formats/IImageInfoDetector.cs

@ -14,10 +14,14 @@ namespace SixLabors.ImageSharp.Formats
/// <summary>
/// Reads the raw image information from the specified stream.
/// </summary>
/// <param name="configuration">The configuration for the image.</param>
/// <remarks>
/// This method is designed to support the ImageSharp internal infrastructure and is not recommended for direct use.
/// </remarks>
/// <param name="options">The general decoder options.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>The <see cref="PixelTypeInfo"/> object</returns>
IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken);
/// <returns>The <see cref="IImageInfo"/> object.</returns>
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken);
}
}

16
src/ImageSharp/Formats/ISpecializedDecoderOptions.cs

@ -0,0 +1,16 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats
{
/// <summary>
/// Provides specialized configuration options for decoding image formats.
/// </summary>
public interface ISpecializedDecoderOptions
{
/// <summary>
/// Gets or sets the general decoder options.
/// </summary>
DecoderOptions GeneralOptions { get; set; }
}
}

182
src/ImageSharp/Formats/ImageDecoderExtensions.cs

@ -0,0 +1,182 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats
{
/// <summary>
/// Extensions methods for <see cref="IImageDecoder"/> and <see cref="IImageDecoderSpecialized{T}"/>.
/// </summary>
public static class ImageDecoderExtensions
{
/// <summary>
/// Reads the raw image information from the specified stream.
/// </summary>
/// <param name="decoder">The decoder.</param>
/// <param name="options">The general decoder options.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <returns>The <see cref="IImageInfo"/> object.</returns>
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
public static IImageInfo Identify(this IImageDecoder decoder, DecoderOptions options, Stream stream)
=> Image.WithSeekableStream(
options,
stream,
s => decoder.Identify(options, s, default));
/// <summary>
/// Reads the raw image information from the specified stream.
/// </summary>
/// <param name="decoder">The decoder.</param>
/// <param name="options">The general decoder options.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>The <see cref="Task{IImageInfo}"/> object.</returns>
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
public static Task<IImageInfo> IdentifyAsync(this IImageDecoder decoder, DecoderOptions options, Stream stream, CancellationToken cancellationToken = default)
=> Image.WithSeekableStreamAsync(
options,
stream,
(s, ct) => decoder.Identify(options, s, ct),
cancellationToken);
/// <summary>
/// Decodes the image from the specified stream to an <see cref="Image{TPixel}"/> of a specific pixel type.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="decoder">The decoder.</param>
/// <param name="options">The general decoder options.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
public static Image<TPixel> Decode<TPixel>(this IImageDecoder decoder, DecoderOptions options, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
=> Image.WithSeekableStream(
options,
stream,
s => decoder.Decode<TPixel>(options, s, default));
/// <summary>
/// Decodes the image from the specified stream to an <see cref="Image"/> of a specific pixel type.
/// </summary>
/// <param name="decoder">The decoder.</param>
/// <param name="options">The general decoder options.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
public static Image Decode(this IImageDecoder decoder, DecoderOptions options, Stream stream)
=> Image.WithSeekableStream(
options,
stream,
s => decoder.Decode(options, s, default));
/// <summary>
/// Decodes the image from the specified stream to an <see cref="Image{TPixel}"/> of a specific pixel type.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="decoder">The decoder.</param>
/// <param name="options">The general decoder options.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
public static Task<Image<TPixel>> DecodeAsync<TPixel>(this IImageDecoder decoder, DecoderOptions options, Stream stream, CancellationToken cancellationToken = default)
where TPixel : unmanaged, IPixel<TPixel>
=> Image.WithSeekableStreamAsync(
options,
stream,
(s, ct) => decoder.Decode<TPixel>(options, s, ct),
cancellationToken);
/// <summary>
/// Decodes the image from the specified stream to an <see cref="Image"/> of a specific pixel type.
/// </summary>
/// <param name="decoder">The decoder.</param>
/// <param name="options">The general decoder options.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
public static Task<Image> DecodeAsync(this IImageDecoder decoder, DecoderOptions options, Stream stream, CancellationToken cancellationToken = default)
=> Image.WithSeekableStreamAsync(
options,
stream,
(s, ct) => decoder.Decode(options, s, ct),
cancellationToken);
/// <summary>
/// Decodes the image from the specified stream to an <see cref="Image{TPixel}"/> of a specific pixel type.
/// </summary>
/// <typeparam name="T">The type of specialized options.</typeparam>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="decoder">The decoder.</param>
/// <param name="options">The specialized decoder options.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
public static Image<TPixel> Decode<T, TPixel>(this IImageDecoderSpecialized<T> decoder, T options, Stream stream)
where T : ISpecializedDecoderOptions
where TPixel : unmanaged, IPixel<TPixel>
=> Image.WithSeekableStream(
options.GeneralOptions,
stream,
s => decoder.Decode<TPixel>(options, s, default));
/// <summary>
/// Decodes the image from the specified stream to an <see cref="Image"/> of a specific pixel type.
/// </summary>
/// <typeparam name="T">The type of specialized options.</typeparam>
/// <param name="decoder">The decoder.</param>
/// <param name="options">The specialized decoder options.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
public static Image Decode<T>(this IImageDecoderSpecialized<T> decoder, T options, Stream stream)
where T : ISpecializedDecoderOptions
=> Image.WithSeekableStream(
options.GeneralOptions,
stream,
s => decoder.Decode(options, s, default));
/// <summary>
/// Decodes the image from the specified stream to an <see cref="Image{TPixel}"/> of a specific pixel type.
/// </summary>
/// <typeparam name="T">The type of specialized options.</typeparam>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="decoder">The decoder.</param>
/// <param name="options">The specialized decoder options.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
public static Task<Image<TPixel>> DecodeAsync<T, TPixel>(this IImageDecoderSpecialized<T> decoder, T options, Stream stream, CancellationToken cancellationToken = default)
where T : ISpecializedDecoderOptions
where TPixel : unmanaged, IPixel<TPixel>
=> Image.WithSeekableStreamAsync(
options.GeneralOptions,
stream,
(s, ct) => decoder.Decode<TPixel>(options, s, ct),
cancellationToken);
/// <summary>
/// Decodes the image from the specified stream to an <see cref="Image"/> of a specific pixel type.
/// </summary>
/// <typeparam name="T">The type of specialized options.</typeparam>
/// <param name="decoder">The decoder.</param>
/// <param name="options">The specialized decoder options.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
public static Task<Image> DecodeAsync<T>(this IImageDecoderSpecialized<T> decoder, T options, Stream stream, CancellationToken cancellationToken = default)
where T : ISpecializedDecoderOptions
=> Image.WithSeekableStreamAsync(
options.GeneralOptions,
stream,
(s, ct) => decoder.Decode(options, s, ct),
cancellationToken);
}
}

49
src/ImageSharp/Formats/ImageDecoderUtilities.cs

@ -7,12 +7,55 @@ using System.Threading;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
namespace SixLabors.ImageSharp.Formats
{
/// <summary>
/// Utility methods for <see cref="IImageDecoder"/>.
/// </summary>
internal static class ImageDecoderUtilities
{
public static IImageInfo Identify(
/// <summary>
/// Performs a resize operation against the decoded image. If the target size is not set, or the image size
/// already matches the target size, the image is untouched.
/// </summary>
/// <param name="options">The decoder options.</param>
/// <param name="image">The decoded image.</param>
public static void Resize(DecoderOptions options, Image image)
{
if (ShouldResize(options, image))
{
ResizeOptions resizeOptions = new()
{
Size = options.TargetSize.Value,
Sampler = options.Sampler,
Mode = ResizeMode.Max
};
image.Mutate(x => x.Resize(resizeOptions));
}
}
/// <summary>
/// Determines whether the decoded image should be resized.
/// </summary>
/// <param name="options">The decoder options.</param>
/// <param name="image">The decoded image.</param>
/// <returns><see langword="true"/> if the image should be resized, otherwise; <see langword="false"/>.</returns>
private static bool ShouldResize(DecoderOptions options, Image image)
{
if (options.TargetSize is null)
{
return false;
}
Size targetSize = options.TargetSize.Value;
Size currentSize = image.Size();
return currentSize.Width != targetSize.Width && currentSize.Height != targetSize.Height;
}
internal static IImageInfo Identify(
this IImageDecoderInternals decoder,
Configuration configuration,
Stream stream,
@ -30,7 +73,7 @@ namespace SixLabors.ImageSharp.Formats
}
}
public static Image<TPixel> Decode<TPixel>(
internal static Image<TPixel> Decode<TPixel>(
this IImageDecoderInternals decoder,
Configuration configuration,
Stream stream,
@ -38,7 +81,7 @@ namespace SixLabors.ImageSharp.Formats
where TPixel : unmanaged, IPixel<TPixel>
=> decoder.Decode<TPixel>(configuration, stream, DefaultLargeImageExceptionFactory, cancellationToken);
public static Image<TPixel> Decode<TPixel>(
internal static Image<TPixel> Decode<TPixel>(
this IImageDecoderInternals decoder,
Configuration configuration,
Stream stream,

16
src/ImageSharp/Formats/Jpeg/IJpegDecoderOptions.cs

@ -1,16 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Jpeg
{
/// <summary>
/// Image decoder for generating an image out of a jpg stream.
/// </summary>
internal interface IJpegDecoderOptions
{
/// <summary>
/// Gets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
bool IgnoreMetadata { get; }
}
}

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

@ -3,66 +3,52 @@
using System.IO;
using System.Threading;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg
{
/// <summary>
/// Image decoder for generating an image out of a jpg stream.
/// Decoder for generating an image out of a jpeg encoded stream.
/// </summary>
public sealed class JpegDecoder : IImageDecoder, IJpegDecoderOptions, IImageInfoDetector
public sealed class JpegDecoder : IImageDecoderSpecialized<JpegDecoderOptions>
{
/// <inheritdoc/>
public bool IgnoreMetadata { get; set; }
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));
using var decoder = new JpegDecoderCore(configuration, this);
return decoder.Decode<TPixel>(configuration, stream, cancellationToken);
using JpegDecoderCore decoder = new(new() { GeneralOptions = options });
return decoder.Identify(options.Configuration, stream, cancellationToken);
}
/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgb24>(configuration, stream, cancellationToken);
/// <inheritdoc/>
Image<TPixel> IImageDecoder.Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> ((IImageDecoderSpecialized<JpegDecoderOptions>)this).Decode<TPixel>(new() { GeneralOptions = options }, stream, cancellationToken);
/// <summary>
/// Decodes and downscales the image from the specified stream if possible.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="configuration">Configuration.</param>
/// <param name="stream">Stream.</param>
/// <param name="targetSize">Target size.</param>
/// <param name="cancellationToken">Cancellation token.</param>
internal Image<TPixel> DecodeInto<TPixel>(Configuration configuration, Stream stream, Size targetSize, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
/// <inheritdoc/>
Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> ((IImageDecoderSpecialized<JpegDecoderOptions>)this).Decode(new() { GeneralOptions = options }, stream, cancellationToken);
/// <inheritdoc/>
Image<TPixel> IImageDecoderSpecialized<JpegDecoderOptions>.Decode<TPixel>(JpegDecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));
using var decoder = new JpegDecoderCore(configuration, this);
using var bufferedReadStream = new BufferedReadStream(configuration, stream);
try
{
return decoder.DecodeInto<TPixel>(bufferedReadStream, targetSize, cancellationToken);
}
catch (InvalidMemoryOperationException ex)
using JpegDecoderCore decoder = new(options);
Image<TPixel> image = decoder.Decode<TPixel>(options.GeneralOptions.Configuration, stream, cancellationToken);
if (options.ResizeMode != JpegDecoderResizeMode.IdctOnly)
{
throw new InvalidImageContentException(((IImageDecoderInternals)decoder).Dimensions, ex);
ImageDecoderUtilities.Resize(options.GeneralOptions, image);
}
return image;
}
/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(stream, nameof(stream));
using var decoder = new JpegDecoderCore(configuration, this);
return decoder.Identify(configuration, stream, cancellationToken);
}
Image IImageDecoderSpecialized<JpegDecoderOptions>.Decode(JpegDecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> ((IImageDecoderSpecialized<JpegDecoderOptions>)this).Decode<Rgb24>(options, stream, cancellationToken);
}
}

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

@ -116,31 +116,42 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
private int? resetInterval;
/// <summary>
/// Initializes a new instance of the <see cref="JpegDecoderCore" /> class.
/// The global configuration.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="options">The options.</param>
public JpegDecoderCore(Configuration configuration, IJpegDecoderOptions options)
{
this.Configuration = configuration ?? Configuration.Default;
this.IgnoreMetadata = options.IgnoreMetadata;
}
private readonly Configuration configuration;
/// <inheritdoc />
public Configuration Configuration { get; }
/// <summary>
/// Whether to skip metadata during decode.
/// </summary>
private readonly bool skipMetadata;
/// <summary>
/// Gets the frame
/// The jpeg specific resize options.
/// </summary>
public JpegFrame Frame { get; private set; }
private readonly JpegDecoderResizeMode resizeMode;
/// <summary>
/// Initializes a new instance of the <see cref="JpegDecoderCore"/> class.
/// </summary>
/// <param name="options">The decoder options.</param>
public JpegDecoderCore(JpegDecoderOptions options)
{
this.Options = options.GeneralOptions;
this.resizeMode = options.ResizeMode;
this.configuration = options.GeneralOptions.Configuration;
this.skipMetadata = options.GeneralOptions.SkipMetadata;
}
/// <inheritdoc />
public DecoderOptions Options { get; }
/// <inheritdoc/>
Size IImageDecoderInternals.Dimensions => this.Frame.PixelSize;
public Size Dimensions => this.Frame.PixelSize;
/// <summary>
/// Gets a value indicating whether the metadata should be ignored when the image is being decoded.
/// Gets the frame
/// </summary>
public bool IgnoreMetadata { get; }
public JpegFrame Frame { get; private set; }
/// <summary>
/// Gets the <see cref="ImageMetadata"/> decoded by this decoder instance.
@ -201,48 +212,33 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
=> this.Decode<TPixel>(stream, targetSize: null, cancellationToken);
/// <inheritdoc/>
public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
this.ParseStream(stream, spectralConverter: null, cancellationToken);
using var spectralConverter = new SpectralConverter<TPixel>(this.configuration, this.resizeMode == JpegDecoderResizeMode.ScaleOnly ? null : this.Options.TargetSize);
this.ParseStream(stream, spectralConverter, cancellationToken);
this.InitExifProfile();
this.InitIccProfile();
this.InitIptcProfile();
this.InitXmpProfile();
this.InitDerivedMetadataProperties();
Size pixelSize = this.Frame.PixelSize;
return new ImageInfo(new PixelTypeInfo(this.Frame.BitsPerPixel), pixelSize.Width, pixelSize.Height, this.Metadata);
return new Image<TPixel>(
this.configuration,
spectralConverter.GetPixelBuffer(cancellationToken),
this.Metadata);
}
/// <summary>
/// Decodes and downscales the image from the specified stream if possible.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">Stream.</param>
/// <param name="targetSize">Target size.</param>
/// <param name="cancellationToken">Cancellation token.</param>
internal Image<TPixel> DecodeInto<TPixel>(BufferedReadStream stream, Size targetSize, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
=> this.Decode<TPixel>(stream, targetSize, cancellationToken);
private Image<TPixel> Decode<TPixel>(BufferedReadStream stream, Size? targetSize, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
/// <inheritdoc/>
public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
using var spectralConverter = new SpectralConverter<TPixel>(this.Configuration, targetSize);
this.ParseStream(stream, spectralConverter, cancellationToken);
this.ParseStream(stream, spectralConverter: null, cancellationToken);
this.InitExifProfile();
this.InitIccProfile();
this.InitIptcProfile();
this.InitXmpProfile();
this.InitDerivedMetadataProperties();
return new Image<TPixel>(
this.Configuration,
spectralConverter.GetPixelBuffer(cancellationToken),
this.Metadata);
Size pixelSize = this.Frame.PixelSize;
return new ImageInfo(new PixelTypeInfo(this.Frame.BitsPerPixel), pixelSize.Width, pixelSize.Height, this.Metadata);
}
/// <summary>
@ -262,7 +258,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
using var ms = new MemoryStream(tableBytes);
using var stream = new BufferedReadStream(this.Configuration, ms);
using var stream = new BufferedReadStream(this.configuration, ms);
// Check for the Start Of Image marker.
int bytesRead = stream.Read(this.markerBuffer, 0, 2);
@ -778,7 +774,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{
const int ExifMarkerLength = 6;
const int XmpMarkerLength = 29;
if (remaining < ExifMarkerLength || this.IgnoreMetadata)
if (remaining < ExifMarkerLength || this.skipMetadata)
{
// Skip the application header length.
stream.Skip(remaining);
@ -816,7 +812,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
if (ProfileResolver.IsProfile(this.temp, ProfileResolver.XmpMarker.Slice(0, ExifMarkerLength)))
{
const int remainingXmpMarkerBytes = XmpMarkerLength - ExifMarkerLength;
if (remaining < remainingXmpMarkerBytes || this.IgnoreMetadata)
if (remaining < remainingXmpMarkerBytes || this.skipMetadata)
{
// Skip the application header length.
stream.Skip(remaining);
@ -858,7 +854,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{
// Length is 14 though we only need to check 12.
const int Icclength = 14;
if (remaining < Icclength || this.IgnoreMetadata)
if (remaining < Icclength || this.skipMetadata)
{
stream.Skip(remaining);
return;
@ -899,7 +895,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <param name="remaining">The remaining bytes in the segment block.</param>
private void ProcessApp13Marker(BufferedReadStream stream, int remaining)
{
if (remaining < ProfileResolver.AdobePhotoshopApp13Marker.Length || this.IgnoreMetadata)
if (remaining < ProfileResolver.AdobePhotoshopApp13Marker.Length || this.skipMetadata)
{
stream.Skip(remaining);
return;
@ -1283,8 +1279,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
IJpegComponent component = decodingComponentType is ComponentType.Huffman ?
new JpegComponent(this.Configuration.MemoryAllocator, this.Frame, componentId, h, v, quantTableIndex, i) :
new ArithmeticDecodingComponent(this.Configuration.MemoryAllocator, this.Frame, componentId, h, v, quantTableIndex, i);
new JpegComponent(this.configuration.MemoryAllocator, this.Frame, componentId, h, v, quantTableIndex, i) :
new ArithmeticDecodingComponent(this.configuration.MemoryAllocator, this.Frame, componentId, h, v, quantTableIndex, i);
this.Frame.Components[i] = (JpegComponent)component;
this.Frame.ComponentIds[i] = componentId;
@ -1323,7 +1319,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
int length = remaining;
using (IMemoryOwner<byte> buffer = this.Configuration.MemoryAllocator.Allocate<byte>(totalBufferSize))
using (IMemoryOwner<byte> buffer = this.configuration.MemoryAllocator.Allocate<byte>(totalBufferSize))
{
Span<byte> bufferSpan = buffer.GetSpan();
Span<byte> huffmanLengthsSpan = bufferSpan.Slice(0, codeLengthsByteSize);

19
src/ImageSharp/Formats/Jpeg/JpegDecoderOptions.cs

@ -0,0 +1,19 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Jpeg
{
/// <summary>
/// Configuration options for decoding Jpeg images.
/// </summary>
public sealed class JpegDecoderOptions : ISpecializedDecoderOptions
{
/// <summary>
/// Gets or sets the resize mode.
/// </summary>
public JpegDecoderResizeMode ResizeMode { get; set; }
/// <inheritdoc/>
public DecoderOptions GeneralOptions { get; set; } = new();
}
}

29
src/ImageSharp/Formats/Jpeg/JpegDecoderResizeMode.cs

@ -0,0 +1,29 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Processing;
namespace SixLabors.ImageSharp.Formats.Jpeg
{
/// <summary>
/// Provides enumeration for resize modes taken during decoding.
/// Applicable only when <see cref="DecoderOptions.TargetSize"/> has a value.
/// </summary>
public enum JpegDecoderResizeMode
{
/// <summary>
/// Both <see cref="IdctOnly"/> and <see cref="ScaleOnly"/>.
/// </summary>
Combined,
/// <summary>
/// IDCT-only to nearest block scale. Similar in output to <see cref="KnownResamplers.Box"/>.
/// </summary>
IdctOnly,
/// <summary>
/// Opt-out the IDCT part and only Resize. Can be useful in case of quality concerns.
/// </summary>
ScaleOnly
}
}

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

@ -26,29 +26,33 @@ namespace SixLabors.ImageSharp.Formats.Pbm
/// </list>
/// The specification of these images is found at <seealso href="http://netpbm.sourceforge.net/doc/pnm.html"/>.
/// </summary>
public sealed class PbmDecoder : IImageDecoder, IImageInfoDetector
public sealed class PbmDecoder : IImageDecoder
{
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));
var decoder = new PbmDecoderCore(configuration);
return decoder.Decode<TPixel>(configuration, stream, cancellationToken);
return new PbmDecoderCore(options).Identify(options.Configuration, stream, cancellationToken);
}
/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgb24>(configuration, stream, cancellationToken);
/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken)
Image<TPixel> IImageDecoder.Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));
var decoder = new PbmDecoderCore(configuration);
return decoder.Identify(configuration, stream, cancellationToken);
PbmDecoderCore decoder = new(options);
Image<TPixel> image = decoder.Decode<TPixel>(options.Configuration, stream, cancellationToken);
ImageDecoderUtilities.Resize(options, image);
return image;
}
/// <inheritdoc />
Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> ((IImageDecoder)this).Decode<Rgb24>(options, stream, cancellationToken);
}
}

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

@ -19,43 +19,50 @@ namespace SixLabors.ImageSharp.Formats.Pbm
private int maxPixelValue;
/// <summary>
/// Initializes a new instance of the <see cref="PbmDecoderCore" /> class.
/// The general configuration.
/// </summary>
/// <param name="configuration">The configuration.</param>
public PbmDecoderCore(Configuration configuration) => this.Configuration = configuration ?? Configuration.Default;
private readonly Configuration configuration;
/// <inheritdoc />
public Configuration Configuration { get; }
/// <summary>
/// The colortype to use
/// </summary>
private PbmColorType colorType;
/// <summary>
/// Gets the colortype to use
/// The size of the pixel array
/// </summary>
public PbmColorType ColorType { get; private set; }
private Size pixelSize;
/// <summary>
/// Gets the size of the pixel array
/// The component data type
/// </summary>
public Size PixelSize { get; private set; }
private PbmComponentType componentType;
/// <summary>
/// Gets the component data type
/// The Encoding of pixels
/// </summary>
public PbmComponentType ComponentType { get; private set; }
private PbmEncoding encoding;
/// <summary>
/// Gets the Encoding of pixels
/// The <see cref="ImageMetadata"/> decoded by this decoder instance.
/// </summary>
public PbmEncoding Encoding { get; private set; }
private ImageMetadata metadata;
/// <summary>
/// Gets the <see cref="ImageMetadata"/> decoded by this decoder instance.
/// Initializes a new instance of the <see cref="PbmDecoderCore" /> class.
/// </summary>
public ImageMetadata Metadata { get; private set; }
/// <param name="options">The decoder options.</param>
public PbmDecoderCore(DecoderOptions options)
{
this.Options = options;
this.configuration = options.Configuration;
}
/// <inheritdoc/>
Size IImageDecoderInternals.Dimensions => this.PixelSize;
public DecoderOptions Options { get; }
private bool NeedsUpscaling => this.ColorType != PbmColorType.BlackAndWhite && this.maxPixelValue is not 255 and not 65535;
/// <inheritdoc/>
public Size Dimensions => this.pixelSize;
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
@ -63,12 +70,12 @@ namespace SixLabors.ImageSharp.Formats.Pbm
{
this.ProcessHeader(stream);
var image = new Image<TPixel>(this.Configuration, this.PixelSize.Width, this.PixelSize.Height, this.Metadata);
var image = new Image<TPixel>(this.configuration, this.pixelSize.Width, this.pixelSize.Height, this.metadata);
Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer();
this.ProcessPixels(stream, pixels);
if (this.NeedsUpscaling)
if (this.NeedsUpscaling())
{
this.ProcessUpscaling(image);
}
@ -82,8 +89,8 @@ namespace SixLabors.ImageSharp.Formats.Pbm
this.ProcessHeader(stream);
// BlackAndWhite pixels are encoded into a byte.
int bitsPerPixel = this.ComponentType == PbmComponentType.Short ? 16 : 8;
return new ImageInfo(new PixelTypeInfo(bitsPerPixel), this.PixelSize.Width, this.PixelSize.Height, this.Metadata);
int bitsPerPixel = this.componentType == PbmComponentType.Short ? 16 : 8;
return new ImageInfo(new PixelTypeInfo(bitsPerPixel), this.pixelSize.Width, this.pixelSize.Height, this.metadata);
}
/// <summary>
@ -104,33 +111,33 @@ namespace SixLabors.ImageSharp.Formats.Pbm
{
case '1':
// Plain PBM format: 1 component per pixel, boolean value ('0' or '1').
this.ColorType = PbmColorType.BlackAndWhite;
this.Encoding = PbmEncoding.Plain;
this.colorType = PbmColorType.BlackAndWhite;
this.encoding = PbmEncoding.Plain;
break;
case '2':
// Plain PGM format: 1 component per pixel, in decimal text.
this.ColorType = PbmColorType.Grayscale;
this.Encoding = PbmEncoding.Plain;
this.colorType = PbmColorType.Grayscale;
this.encoding = PbmEncoding.Plain;
break;
case '3':
// Plain PPM format: 3 components per pixel, in decimal text.
this.ColorType = PbmColorType.Rgb;
this.Encoding = PbmEncoding.Plain;
this.colorType = PbmColorType.Rgb;
this.encoding = PbmEncoding.Plain;
break;
case '4':
// Binary PBM format: 1 component per pixel, 8 pixels per byte.
this.ColorType = PbmColorType.BlackAndWhite;
this.Encoding = PbmEncoding.Binary;
this.colorType = PbmColorType.BlackAndWhite;
this.encoding = PbmEncoding.Binary;
break;
case '5':
// Binary PGM format: 1 components per pixel, in binary integers.
this.ColorType = PbmColorType.Grayscale;
this.Encoding = PbmEncoding.Binary;
this.colorType = PbmColorType.Grayscale;
this.encoding = PbmEncoding.Binary;
break;
case '6':
// Binary PPM format: 3 components per pixel, in binary integers.
this.ColorType = PbmColorType.Rgb;
this.Encoding = PbmEncoding.Binary;
this.colorType = PbmColorType.Rgb;
this.encoding = PbmEncoding.Binary;
break;
case '7':
// PAM image: sequence of images.
@ -144,52 +151,54 @@ namespace SixLabors.ImageSharp.Formats.Pbm
stream.SkipWhitespaceAndComments();
int height = stream.ReadDecimal();
stream.SkipWhitespaceAndComments();
if (this.ColorType != PbmColorType.BlackAndWhite)
if (this.colorType != PbmColorType.BlackAndWhite)
{
this.maxPixelValue = stream.ReadDecimal();
if (this.maxPixelValue > 255)
{
this.ComponentType = PbmComponentType.Short;
this.componentType = PbmComponentType.Short;
}
else
{
this.ComponentType = PbmComponentType.Byte;
this.componentType = PbmComponentType.Byte;
}
stream.SkipWhitespaceAndComments();
}
else
{
this.ComponentType = PbmComponentType.Bit;
this.componentType = PbmComponentType.Bit;
}
this.PixelSize = new Size(width, height);
this.Metadata = new ImageMetadata();
PbmMetadata meta = this.Metadata.GetPbmMetadata();
meta.Encoding = this.Encoding;
meta.ColorType = this.ColorType;
meta.ComponentType = this.ComponentType;
this.pixelSize = new Size(width, height);
this.metadata = new ImageMetadata();
PbmMetadata meta = this.metadata.GetPbmMetadata();
meta.Encoding = this.encoding;
meta.ColorType = this.colorType;
meta.ComponentType = this.componentType;
}
private void ProcessPixels<TPixel>(BufferedReadStream stream, Buffer2D<TPixel> pixels)
where TPixel : unmanaged, IPixel<TPixel>
{
if (this.Encoding == PbmEncoding.Binary)
if (this.encoding == PbmEncoding.Binary)
{
BinaryDecoder.Process(this.Configuration, pixels, stream, this.ColorType, this.ComponentType);
BinaryDecoder.Process(this.configuration, pixels, stream, this.colorType, this.componentType);
}
else
{
PlainDecoder.Process(this.Configuration, pixels, stream, this.ColorType, this.ComponentType);
PlainDecoder.Process(this.configuration, pixels, stream, this.colorType, this.componentType);
}
}
private void ProcessUpscaling<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
int maxAllocationValue = this.ComponentType == PbmComponentType.Short ? 65535 : 255;
int maxAllocationValue = this.componentType == PbmComponentType.Short ? 65535 : 255;
float factor = maxAllocationValue / this.maxPixelValue;
image.Mutate(x => x.Brightness(factor));
}
private bool NeedsUpscaling() => this.colorType != PbmColorType.BlackAndWhite && this.maxPixelValue is not 255 and not 65535;
}
}

16
src/ImageSharp/Formats/Png/IPngDecoderOptions.cs

@ -1,16 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Png
{
/// <summary>
/// The options for decoding png images
/// </summary>
internal interface IPngDecoderOptions
{
/// <summary>
/// Gets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
bool IgnoreMetadata { get; }
}
}

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

@ -10,78 +10,88 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary>
/// Decoder for generating an image out of a png encoded stream.
/// </summary>
public sealed class PngDecoder : IImageDecoder, IPngDecoderOptions, IImageInfoDetector
public sealed class PngDecoder : IImageDecoder
{
/// <inheritdoc/>
public bool IgnoreMetadata { get; set; }
IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));
return new PngDecoderCore(options).Identify(options.Configuration, stream, cancellationToken);
}
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
Image<TPixel> IImageDecoder.Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
PngDecoderCore decoder = new(configuration, this);
return decoder.Decode<TPixel>(configuration, stream, cancellationToken);
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));
PngDecoderCore decoder = new(options);
Image<TPixel> image = decoder.Decode<TPixel>(options.Configuration, stream, cancellationToken);
ImageDecoderUtilities.Resize(options, image);
return image;
}
/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken)
/// <inheritdoc/>
Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
PngDecoderCore decoder = new(configuration, true);
IImageInfo info = decoder.Identify(configuration, stream, cancellationToken);
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));
PngDecoderCore decoder = new(options, true);
IImageInfo info = decoder.Identify(options.Configuration, stream, cancellationToken);
stream.Position = 0;
PngMetadata meta = info.Metadata.GetPngMetadata();
PngColorType color = meta.ColorType.GetValueOrDefault();
PngBitDepth bits = meta.BitDepth.GetValueOrDefault();
var imageDecoder = (IImageDecoder)this;
switch (color)
{
case PngColorType.Grayscale:
if (bits == PngBitDepth.Bit16)
{
return !meta.HasTransparency
? this.Decode<L16>(configuration, stream, cancellationToken)
: this.Decode<La32>(configuration, stream, cancellationToken);
? imageDecoder.Decode<L16>(options, stream, cancellationToken)
: imageDecoder.Decode<La32>(options, stream, cancellationToken);
}
return !meta.HasTransparency
? this.Decode<L8>(configuration, stream, cancellationToken)
: this.Decode<La16>(configuration, stream, cancellationToken);
? imageDecoder.Decode<L8>(options, stream, cancellationToken)
: imageDecoder.Decode<La16>(options, stream, cancellationToken);
case PngColorType.Rgb:
if (bits == PngBitDepth.Bit16)
{
return !meta.HasTransparency
? this.Decode<Rgb48>(configuration, stream, cancellationToken)
: this.Decode<Rgba64>(configuration, stream, cancellationToken);
? imageDecoder.Decode<Rgb48>(options, stream, cancellationToken)
: imageDecoder.Decode<Rgba64>(options, stream, cancellationToken);
}
return !meta.HasTransparency
? this.Decode<Rgb24>(configuration, stream, cancellationToken)
: this.Decode<Rgba32>(configuration, stream, cancellationToken);
? imageDecoder.Decode<Rgb24>(options, stream, cancellationToken)
: imageDecoder.Decode<Rgba32>(options, stream, cancellationToken);
case PngColorType.Palette:
return this.Decode<Rgba32>(configuration, stream, cancellationToken);
return imageDecoder.Decode<Rgba32>(options, stream, cancellationToken);
case PngColorType.GrayscaleWithAlpha:
return (bits == PngBitDepth.Bit16)
? this.Decode<La32>(configuration, stream, cancellationToken)
: this.Decode<La16>(configuration, stream, cancellationToken);
? imageDecoder.Decode<La32>(options, stream, cancellationToken)
: imageDecoder.Decode<La16>(options, stream, cancellationToken);
case PngColorType.RgbWithAlpha:
return (bits == PngBitDepth.Bit16)
? this.Decode<Rgba64>(configuration, stream, cancellationToken)
: this.Decode<Rgba32>(configuration, stream, cancellationToken);
? imageDecoder.Decode<Rgba64>(options, stream, cancellationToken)
: imageDecoder.Decode<Rgba32>(options, stream, cancellationToken);
default:
return this.Decode<Rgba32>(configuration, stream, cancellationToken);
return imageDecoder.Decode<Rgba32>(options, stream, cancellationToken);
}
}
/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
PngDecoderCore decoder = new(configuration, this);
return decoder.Identify(configuration, stream, cancellationToken);
}
}
}

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

@ -34,10 +34,15 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary>
private readonly byte[] buffer = new byte[4];
/// <summary>
/// The general decoder options.
/// </summary>
private readonly Configuration configuration;
/// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
private readonly bool ignoreMetadata;
private readonly bool skipMetadata;
/// <summary>
/// Gets or sets a value indicating whether to read the IHDR and tRNS chunks only.
@ -117,29 +122,28 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary>
/// Initializes a new instance of the <see cref="PngDecoderCore"/> class.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="options">The decoder options.</param>
public PngDecoderCore(Configuration configuration, IPngDecoderOptions options)
public PngDecoderCore(DecoderOptions options)
{
this.Configuration = configuration ?? Configuration.Default;
this.memoryAllocator = this.Configuration.MemoryAllocator;
this.ignoreMetadata = options.IgnoreMetadata;
this.Options = options;
this.configuration = options.Configuration;
this.skipMetadata = options.SkipMetadata;
this.memoryAllocator = this.configuration.MemoryAllocator;
}
internal PngDecoderCore(Configuration configuration, bool colorMetadataOnly)
internal PngDecoderCore(DecoderOptions options, bool colorMetadataOnly)
{
this.Configuration = configuration ?? Configuration.Default;
this.memoryAllocator = this.Configuration.MemoryAllocator;
this.Options = options;
this.colorMetadataOnly = colorMetadataOnly;
this.ignoreMetadata = true;
this.skipMetadata = true;
this.configuration = options.Configuration;
this.memoryAllocator = this.configuration.MemoryAllocator;
}
/// <inheritdoc/>
public Configuration Configuration { get; }
public DecoderOptions Options { get; }
/// <summary>
/// Gets the dimensions of the image.
/// </summary>
/// <inheritdoc/>
public Size Dimensions => new(this.header.Width, this.header.Height);
/// <inheritdoc/>
@ -198,7 +202,7 @@ namespace SixLabors.ImageSharp.Formats.Png
this.ReadInternationalTextChunk(metadata, chunk.Data.GetSpan());
break;
case PngChunkType.Exif:
if (!this.ignoreMetadata)
if (!this.skipMetadata)
{
byte[] exifData = new byte[chunk.Length];
chunk.Data.GetSpan().CopyTo(exifData);
@ -335,7 +339,7 @@ namespace SixLabors.ImageSharp.Formats.Png
break;
}
if (!this.ignoreMetadata)
if (!this.skipMetadata)
{
byte[] exifData = new byte[chunk.Length];
chunk.Data.GetSpan().CopyTo(exifData);
@ -468,7 +472,7 @@ namespace SixLabors.ImageSharp.Formats.Png
where TPixel : unmanaged, IPixel<TPixel>
{
image = Image.CreateUninitialized<TPixel>(
this.Configuration,
this.configuration,
this.header.Width,
this.header.Height,
metadata);
@ -484,7 +488,7 @@ namespace SixLabors.ImageSharp.Formats.Png
this.previousScanline?.Dispose();
this.scanline?.Dispose();
this.previousScanline = this.memoryAllocator.Allocate<byte>(this.bytesPerScanline, AllocationOptions.Clean);
this.scanline = this.Configuration.MemoryAllocator.Allocate<byte>(this.bytesPerScanline, AllocationOptions.Clean);
this.scanline = this.configuration.MemoryAllocator.Allocate<byte>(this.bytesPerScanline, AllocationOptions.Clean);
}
/// <summary>
@ -797,7 +801,7 @@ namespace SixLabors.ImageSharp.Formats.Png
case PngColorType.Rgb:
PngScanlineProcessor.ProcessRgbScanline(
this.Configuration,
this.configuration,
this.header,
scanlineSpan,
rowSpan,
@ -811,7 +815,7 @@ namespace SixLabors.ImageSharp.Formats.Png
case PngColorType.RgbWithAlpha:
PngScanlineProcessor.ProcessRgbaScanline(
this.Configuration,
this.configuration,
this.header,
scanlineSpan,
rowSpan,
@ -1004,7 +1008,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="data">The <see cref="T:Span"/> containing the data.</param>
private void ReadTextChunk(ImageMetadata baseMetadata, PngMetadata metadata, ReadOnlySpan<byte> data)
{
if (this.ignoreMetadata)
if (this.skipMetadata)
{
return;
}
@ -1039,7 +1043,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="data">The <see cref="T:Span"/> containing the data.</param>
private void ReadCompressedTextChunk(ImageMetadata baseMetadata, PngMetadata metadata, ReadOnlySpan<byte> data)
{
if (this.ignoreMetadata)
if (this.skipMetadata)
{
return;
}
@ -1225,10 +1229,10 @@ namespace SixLabors.ImageSharp.Formats.Png
{
fixed (byte* compressedDataBase = compressedData)
{
using (IMemoryOwner<byte> destBuffer = this.memoryAllocator.Allocate<byte>(this.Configuration.StreamProcessingBufferSize))
using (IMemoryOwner<byte> destBuffer = this.memoryAllocator.Allocate<byte>(this.configuration.StreamProcessingBufferSize))
using (var memoryStreamOutput = new MemoryStream(compressedData.Length))
using (var memoryStreamInput = new UnmanagedMemoryStream(compressedDataBase, compressedData.Length))
using (var bufferedStream = new BufferedReadStream(this.Configuration, memoryStreamInput))
using (var bufferedStream = new BufferedReadStream(this.configuration, memoryStreamInput))
using (var inflateStream = new ZlibInflateStream(bufferedStream))
{
Span<byte> destUncompressedData = destBuffer.GetSpan();
@ -1327,7 +1331,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="data">The <see cref="T:Span"/> containing the data.</param>
private void ReadInternationalTextChunk(ImageMetadata metadata, ReadOnlySpan<byte> data)
{
if (this.ignoreMetadata)
if (this.skipMetadata)
{
return;
}
@ -1566,7 +1570,7 @@ namespace SixLabors.ImageSharp.Formats.Png
private IMemoryOwner<byte> ReadChunkData(int length)
{
// We rent the buffer here to return it afterwards in Decode()
IMemoryOwner<byte> buffer = this.Configuration.MemoryAllocator.Allocate<byte>(length, AllocationOptions.Clean);
IMemoryOwner<byte> buffer = this.configuration.MemoryAllocator.Allocate<byte>(length, AllocationOptions.Clean);
this.currentStream.Read(buffer.GetSpan(), 0, length);

12
src/ImageSharp/Formats/Tga/ITgaDecoderOptions.cs

@ -1,12 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Tga
{
/// <summary>
/// The options for decoding tga images. Currently empty, but this may change in the future.
/// </summary>
internal interface ITgaDecoderOptions
{
}
}

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

@ -10,28 +10,33 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// <summary>
/// Image decoder for Truevision TGA images.
/// </summary>
public sealed class TgaDecoder : IImageDecoder, ITgaDecoderOptions, IImageInfoDetector
public sealed class TgaDecoder : IImageDecoder
{
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));
var decoder = new TgaDecoderCore(configuration, this);
return decoder.Decode<TPixel>(configuration, stream, cancellationToken);
return new TgaDecoderCore(options).Identify(options.Configuration, stream, cancellationToken);
}
/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>(configuration, stream, cancellationToken);
/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken)
Image<TPixel> IImageDecoder.Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));
return new TgaDecoderCore(configuration, this).Identify(configuration, stream, cancellationToken);
TgaDecoderCore decoder = new(options);
Image<TPixel> image = decoder.Decode<TPixel>(options.Configuration, stream, cancellationToken);
ImageDecoderUtilities.Resize(options, image);
return image;
}
/// <inheritdoc/>
Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> ((IImageDecoder)this).Decode<Rgba32>(options, stream, cancellationToken);
}
}

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

@ -22,6 +22,11 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// </summary>
private readonly byte[] scratchBuffer = new byte[4];
/// <summary>
/// General configuration options.
/// </summary>
private readonly Configuration configuration;
/// <summary>
/// The metadata.
/// </summary>
@ -47,11 +52,6 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// </summary>
private BufferedReadStream currentStream;
/// <summary>
/// The bitmap decoder options.
/// </summary>
private readonly ITgaDecoderOptions options;
/// <summary>
/// Indicates whether there is a alpha channel present.
/// </summary>
@ -60,21 +60,18 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// <summary>
/// Initializes a new instance of the <see cref="TgaDecoderCore"/> class.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="options">The options.</param>
public TgaDecoderCore(Configuration configuration, ITgaDecoderOptions options)
public TgaDecoderCore(DecoderOptions options)
{
this.Configuration = configuration;
this.memoryAllocator = configuration.MemoryAllocator;
this.options = options;
this.Options = options;
this.configuration = options.Configuration;
this.memoryAllocator = this.configuration.MemoryAllocator;
}
/// <inheritdoc />
public Configuration Configuration { get; }
public DecoderOptions Options { get; }
/// <summary>
/// Gets the dimensions of the image.
/// </summary>
/// <inheritdoc />
public Size Dimensions => new(this.fileHeader.Width, this.fileHeader.Height);
/// <inheritdoc />
@ -97,7 +94,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
throw new UnknownImageFormatException("Width or height cannot be 0");
}
var image = Image.CreateUninitialized<TPixel>(this.Configuration, this.fileHeader.Width, this.fileHeader.Height, this.metadata);
var image = Image.CreateUninitialized<TPixel>(this.configuration, this.fileHeader.Width, this.fileHeader.Height, this.metadata);
Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer();
if (this.fileHeader.ColorMapType == 1)
@ -463,11 +460,11 @@ namespace SixLabors.ImageSharp.Formats.Tga
if (this.fileHeader.ImageType == TgaImageType.BlackAndWhite)
{
PixelOperations<TPixel>.Instance.FromLa16Bytes(this.Configuration, rowSpan, pixelSpan, width);
PixelOperations<TPixel>.Instance.FromLa16Bytes(this.configuration, rowSpan, pixelSpan, width);
}
else
{
PixelOperations<TPixel>.Instance.FromBgra5551Bytes(this.Configuration, rowSpan, pixelSpan, width);
PixelOperations<TPixel>.Instance.FromBgra5551Bytes(this.configuration, rowSpan, pixelSpan, width);
}
}
}
@ -672,7 +669,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
}
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.FromL8Bytes(this.Configuration, row, pixelSpan, width);
PixelOperations<TPixel>.Instance.FromL8Bytes(this.configuration, row, pixelSpan, width);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -709,7 +706,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
}
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.FromBgr24Bytes(this.Configuration, row, pixelSpan, width);
PixelOperations<TPixel>.Instance.FromBgr24Bytes(this.configuration, row, pixelSpan, width);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -738,7 +735,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
}
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.FromBgra32Bytes(this.Configuration, row, pixelSpan, width);
PixelOperations<TPixel>.Instance.FromBgra32Bytes(this.configuration, row, pixelSpan, width);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]

3
src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs

@ -3,6 +3,7 @@
using System;
using System.IO.Compression;
using System.Threading;
using SixLabors.ImageSharp.Compression.Zlib;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
@ -40,7 +41,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
}
/// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer)
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer, CancellationToken cancellationToken)
{
long pos = stream.Position;
using (var deframeStream = new ZlibInflateStream(

33
src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs

@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// </summary>
internal sealed class JpegTiffCompression : TiffBaseDecompressor
{
private readonly Configuration configuration;
private readonly JpegDecoderOptions options;
private readonly byte[] jpegTables;
@ -27,14 +27,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// <summary>
/// Initializes a new instance of the <see cref="JpegTiffCompression"/> class.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="options">The specialized jpeg decoder options.</param>
/// <param name="memoryAllocator">The memoryAllocator to use for buffer allocations.</param>
/// <param name="width">The image width.</param>
/// <param name="bitsPerPixel">The bits per pixel.</param>
/// <param name="jpegTables">The JPEG tables containing the quantization and/or Huffman tables.</param>
/// <param name="photometricInterpretation">The photometric interpretation.</param>
public JpegTiffCompression(
Configuration configuration,
JpegDecoderOptions options,
MemoryAllocator memoryAllocator,
int width,
int bitsPerPixel,
@ -42,31 +42,29 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
TiffPhotometricInterpretation photometricInterpretation)
: base(memoryAllocator, width, bitsPerPixel)
{
this.configuration = configuration;
this.options = options;
this.jpegTables = jpegTables;
this.photometricInterpretation = photometricInterpretation;
}
/// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer)
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer, CancellationToken cancellationToken)
{
if (this.jpegTables != null)
{
using var jpegDecoder = new JpegDecoderCore(this.configuration, new JpegDecoder());
using var jpegDecoder = new JpegDecoderCore(this.options);
Configuration configuration = this.options.GeneralOptions.Configuration;
switch (this.photometricInterpretation)
{
case TiffPhotometricInterpretation.BlackIsZero:
case TiffPhotometricInterpretation.WhiteIsZero:
{
using SpectralConverter<L8> spectralConverterGray =
new GrayJpegSpectralConverter<L8>(this.configuration);
var scanDecoderGray = new HuffmanScanDecoder(stream, spectralConverterGray, CancellationToken.None);
using SpectralConverter<L8> spectralConverterGray = new GrayJpegSpectralConverter<L8>(configuration);
var scanDecoderGray = new HuffmanScanDecoder(stream, spectralConverterGray, cancellationToken);
jpegDecoder.LoadTables(this.jpegTables, scanDecoderGray);
jpegDecoder.ParseStream(stream, spectralConverterGray, CancellationToken.None);
jpegDecoder.ParseStream(stream, spectralConverterGray, cancellationToken);
// TODO: Should we pass through the CancellationToken from the tiff decoder?
using Buffer2D<L8> decompressedBuffer = spectralConverterGray.GetPixelBuffer(CancellationToken.None);
using Buffer2D<L8> decompressedBuffer = spectralConverterGray.GetPixelBuffer(cancellationToken);
CopyImageBytesToBuffer(buffer, decompressedBuffer);
break;
}
@ -75,13 +73,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
case TiffPhotometricInterpretation.Rgb:
{
using SpectralConverter<Rgb24> spectralConverter =
new TiffJpegSpectralConverter<Rgb24>(this.configuration, this.photometricInterpretation);
var scanDecoder = new HuffmanScanDecoder(stream, spectralConverter, CancellationToken.None);
new TiffJpegSpectralConverter<Rgb24>(configuration, this.photometricInterpretation);
var scanDecoder = new HuffmanScanDecoder(stream, spectralConverter, cancellationToken);
jpegDecoder.LoadTables(this.jpegTables, scanDecoder);
jpegDecoder.ParseStream(stream, spectralConverter, CancellationToken.None);
jpegDecoder.ParseStream(stream, spectralConverter, cancellationToken);
// TODO: Should we pass through the CancellationToken from the tiff decoder?
using Buffer2D<Rgb24> decompressedBuffer = spectralConverter.GetPixelBuffer(CancellationToken.None);
using Buffer2D<Rgb24> decompressedBuffer = spectralConverter.GetPixelBuffer(cancellationToken);
CopyImageBytesToBuffer(buffer, decompressedBuffer);
break;
}

3
src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs

@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License.
using System;
using System.Threading;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
using SixLabors.ImageSharp.IO;
@ -35,7 +36,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
}
/// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer)
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer, CancellationToken cancellationToken)
{
var decoder = new TiffLzwDecoder(stream);
decoder.DecodePixels(buffer);

3
src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs

@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License.
using System;
using System.Threading;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
@ -40,7 +41,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
private TiffFillOrder FillOrder { get; }
/// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer)
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer, CancellationToken cancellationToken)
{
var bitReader = new ModifiedHuffmanBitReader(stream, this.FillOrder, byteCount);

3
src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs

@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License.
using System;
using System.Threading;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
@ -24,7 +25,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
}
/// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer)
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer, CancellationToken cancellationToken)
=> _ = stream.Read(buffer, 0, Math.Min(buffer.Length, byteCount));
/// <inheritdoc/>

3
src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs

@ -3,6 +3,7 @@
using System;
using System.Buffers;
using System.Threading;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
@ -27,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
}
/// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer)
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer, CancellationToken cancellationToken)
{
if (this.compressedDataMemory == null)
{

3
src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs

@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License.
using System;
using System.Threading;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
@ -53,7 +54,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
private TiffFillOrder FillOrder { get; }
/// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer)
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer, CancellationToken cancellationToken)
{
if (this.faxCompressionOptions.HasFlag(FaxCompressionOptions.TwoDimensionalCoding))
{

3
src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6TiffCompression.cs

@ -4,6 +4,7 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
@ -49,7 +50,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
private TiffFillOrder FillOrder { get; }
/// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer)
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer, CancellationToken cancellationToken)
{
int height = stripHeight;
buffer.Clear();

4
src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffJpegSpectralConverter{TPixel}.cs

@ -35,10 +35,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
return JpegColorConverterBase.GetConverter(colorSpace, frame.Precision);
}
/// <remarks>
/// <summary>
/// This converter must be used only for RGB and YCbCr color spaces for performance reasons.
/// For grayscale images <see cref="GrayJpegSpectralConverter{TPixel}"/> must be used.
/// </remarks>
/// </summary>
private static JpegColorSpace GetJpegColorSpaceFromPhotometricInterpretation(TiffPhotometricInterpretation interpretation)
=> interpretation switch
{

13
src/ImageSharp/Formats/Tiff/Compression/Decompressors/WebpTiffCompression.cs

@ -3,6 +3,7 @@
using System;
using System.Runtime.InteropServices;
using System.Threading;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Formats.Webp;
using SixLabors.ImageSharp.IO;
@ -16,22 +17,24 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// </summary>
internal class WebpTiffCompression : TiffBaseDecompressor
{
private readonly DecoderOptions options;
/// <summary>
/// Initializes a new instance of the <see cref="WebpTiffCompression"/> class.
/// </summary>
/// <param name="options">The general decoder options.</param>
/// <param name="memoryAllocator">The memory allocator.</param>
/// <param name="width">The width of the image.</param>
/// <param name="bitsPerPixel">The bits per pixel.</param>
/// <param name="predictor">The predictor.</param>
public WebpTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None)
public WebpTiffCompression(DecoderOptions options, MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None)
: base(memoryAllocator, width, bitsPerPixel, predictor)
{
}
=> this.options = options;
/// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer)
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer, CancellationToken cancellationToken)
{
using var image = Image.Load<Rgb24>(stream, new WebpDecoder());
using Image<Rgb24> image = ((IImageDecoder)new WebpDecoder()).Decode<Rgb24>(this.options, stream, cancellationToken);
CopyImageBytesToBuffer(buffer, image.Frames.RootFrame.PixelBuffer);
}

9
src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs

@ -3,6 +3,7 @@
using System;
using System.IO;
using System.Threading;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
@ -34,13 +35,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
/// <param name="stripByteCount">The number of bytes to read from the input stream.</param>
/// <param name="stripHeight">The height of the strip.</param>
/// <param name="buffer">The output buffer for uncompressed data.</param>
public void Decompress(BufferedReadStream stream, ulong stripOffset, ulong stripByteCount, int stripHeight, Span<byte> buffer)
/// <param name="cancellationToken">The token to monitor cancellation.</param>
public void Decompress(BufferedReadStream stream, ulong stripOffset, ulong stripByteCount, int stripHeight, Span<byte> buffer, CancellationToken cancellationToken)
{
DebugGuard.MustBeLessThanOrEqualTo(stripOffset, (ulong)long.MaxValue, nameof(stripOffset));
DebugGuard.MustBeLessThanOrEqualTo(stripByteCount, (ulong)long.MaxValue, nameof(stripByteCount));
stream.Seek((long)stripOffset, SeekOrigin.Begin);
this.Decompress(stream, (int)stripByteCount, stripHeight, buffer);
this.Decompress(stream, (int)stripByteCount, stripHeight, buffer, cancellationToken);
if ((long)stripOffset + (long)stripByteCount < stream.Position)
{
@ -55,6 +57,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
/// <param name="byteCount">The number of bytes to read from the input stream.</param>
/// <param name="stripHeight">The height of the strip.</param>
/// <param name="buffer">The output buffer for uncompressed data.</param>
protected abstract void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer);
/// <param name="cancellationToken">The token to monitor cancellation.</param>
protected abstract void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer, CancellationToken cancellationToken);
}
}

6
src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs

@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
internal static class TiffDecompressorsFactory
{
public static TiffBaseDecompressor Create(
Configuration configuration,
DecoderOptions options,
TiffDecoderCompressionType method,
MemoryAllocator allocator,
TiffPhotometricInterpretation photometricInterpretation,
@ -58,11 +58,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
case TiffDecoderCompressionType.Jpeg:
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");
return new JpegTiffCompression(configuration, allocator, width, bitsPerPixel, jpegTables, photometricInterpretation);
return new JpegTiffCompression(new() { GeneralOptions = options }, allocator, width, bitsPerPixel, jpegTables, photometricInterpretation);
case TiffDecoderCompressionType.Webp:
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");
return new WebpTiffCompression(allocator, width, bitsPerPixel);
return new WebpTiffCompression(options, allocator, width, bitsPerPixel);
default:
throw TiffThrowHelper.NotSupportedDecompressor(nameof(method));

23
src/ImageSharp/Formats/Tiff/ITiffDecoderOptions.cs

@ -1,23 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Metadata;
namespace SixLabors.ImageSharp.Formats.Tiff
{
/// <summary>
/// Encapsulates the options for the <see cref="TiffDecoder"/>.
/// </summary>
internal interface ITiffDecoderOptions
{
/// <summary>
/// Gets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
bool IgnoreMetadata { get; }
/// <summary>
/// Gets the decoding mode for multi-frame images.
/// </summary>
FrameDecodingMode DecodingMode { get; }
}
}

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

@ -3,7 +3,6 @@
using System.IO;
using System.Threading;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff
@ -11,39 +10,33 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <summary>
/// Image decoder for generating an image out of a TIFF stream.
/// </summary>
public class TiffDecoder : IImageDecoder, ITiffDecoderOptions, IImageInfoDetector
public class TiffDecoder : IImageDecoder
{
/// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
public bool IgnoreMetadata { get; set; }
/// <summary>
/// Gets or sets the decoding mode for multi-frame images.
/// </summary>
public FrameDecodingMode DecodingMode { get; set; }
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));
var decoder = new TiffDecoderCore(configuration, this);
return decoder.Decode<TPixel>(configuration, stream, cancellationToken);
return new TiffDecoderCore(options).Identify(options.Configuration, stream, cancellationToken);
}
/// <inheritdoc/>
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>(configuration, stream, cancellationToken);
/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken)
Image<TPixel> IImageDecoder.Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));
var decoder = new TiffDecoderCore(configuration, this);
return decoder.Identify(configuration, stream, cancellationToken);
TiffDecoderCore decoder = new(options);
Image<TPixel> image = decoder.Decode<TPixel>(options.Configuration, stream, cancellationToken);
ImageDecoderUtilities.Resize(options, image);
return image;
}
/// <inheritdoc/>
Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> ((IImageDecoder)this).Decode<Rgba32>(options, stream, cancellationToken);
}
}

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

@ -22,20 +22,25 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// </summary>
internal class TiffDecoderCore : IImageDecoderInternals
{
/// <summary>
/// General configuration options.
/// </summary>
private readonly Configuration configuration;
/// <summary>
/// Used for allocating memory during processing operations.
/// </summary>
private readonly MemoryAllocator memoryAllocator;
/// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
/// A value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
private readonly bool ignoreMetadata;
private readonly bool skipMetadata;
/// <summary>
/// Gets the decoding mode for multi-frame images
/// The maximum number of frames to decode. Inclusive.
/// </summary>
private readonly FrameDecodingMode decodingMode;
private readonly uint maxFrames;
/// <summary>
/// The stream to decode from.
@ -55,16 +60,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <summary>
/// Initializes a new instance of the <see cref="TiffDecoderCore" /> class.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="options">The decoder options.</param>
public TiffDecoderCore(Configuration configuration, ITiffDecoderOptions options)
public TiffDecoderCore(DecoderOptions options)
{
options ??= new TiffDecoder();
this.Configuration = configuration ?? Configuration.Default;
this.ignoreMetadata = options.IgnoreMetadata;
this.decodingMode = options.DecodingMode;
this.memoryAllocator = this.Configuration.MemoryAllocator;
this.Options = options;
this.configuration = options.Configuration;
this.skipMetadata = options.SkipMetadata;
this.maxFrames = options.MaxFrames;
this.memoryAllocator = this.configuration.MemoryAllocator;
}
/// <summary>
@ -148,7 +151,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
public TiffPredictor Predictor { get; set; }
/// <inheritdoc/>
public Configuration Configuration { get; }
public DecoderOptions Options { get; }
/// <inheritdoc/>
public Size Dimensions { get; private set; }
@ -161,25 +164,26 @@ namespace SixLabors.ImageSharp.Formats.Tiff
try
{
this.inputStream = stream;
var reader = new DirectoryReader(stream, this.Configuration.MemoryAllocator);
var reader = new DirectoryReader(stream, this.configuration.MemoryAllocator);
IEnumerable<ExifProfile> directories = reader.Read();
this.byteOrder = reader.ByteOrder;
this.isBigTiff = reader.IsBigTiff;
uint frameCount = 0;
foreach (ExifProfile ifd in directories)
{
cancellationToken.ThrowIfCancellationRequested();
ImageFrame<TPixel> frame = this.DecodeFrame<TPixel>(ifd, cancellationToken);
frames.Add(frame);
if (this.decodingMode is FrameDecodingMode.First)
if (++frameCount == this.maxFrames)
{
break;
}
}
ImageMetadata metadata = TiffDecoderMetadataCreator.Create(frames, this.ignoreMetadata, reader.ByteOrder, reader.IsBigTiff);
ImageMetadata metadata = TiffDecoderMetadataCreator.Create(frames, this.skipMetadata, reader.ByteOrder, reader.IsBigTiff);
// TODO: Tiff frames can have different sizes.
ImageFrame<TPixel> root = frames[0];
@ -192,7 +196,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
}
}
return new Image<TPixel>(this.Configuration, metadata, frames);
return new Image<TPixel>(this.configuration, metadata, frames);
}
catch
{
@ -209,7 +213,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
this.inputStream = stream;
var reader = new DirectoryReader(stream, this.Configuration.MemoryAllocator);
var reader = new DirectoryReader(stream, this.configuration.MemoryAllocator);
IEnumerable<ExifProfile> directories = reader.Read();
ExifProfile rootFrameExifProfile = directories.First();
@ -233,7 +237,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
where TPixel : unmanaged, IPixel<TPixel>
{
var imageFrameMetaData = new ImageFrameMetadata();
if (!this.ignoreMetadata)
if (!this.skipMetadata)
{
imageFrameMetaData.ExifProfile = tags;
}
@ -245,7 +249,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
int width = GetImageWidth(tags);
int height = GetImageHeight(tags);
var frame = new ImageFrame<TPixel>(this.Configuration, width, height, imageFrameMetaData);
var frame = new ImageFrame<TPixel>(this.configuration, width, height, imageFrameMetaData);
int rowsPerStrip = tags.GetValue(ExifTag.RowsPerStrip) != null ? (int)tags.GetValue(ExifTag.RowsPerStrip).Value : TiffConstants.RowsPerStripInfinity;
@ -369,7 +373,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
}
using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create(
this.Configuration,
this.Options,
this.CompressionType,
this.memoryAllocator,
this.PhotometricInterpretation,
@ -406,7 +410,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff
stripOffsets[stripIndex],
stripByteCounts[stripIndex],
stripHeight,
stripBuffers[planeIndex].GetSpan());
stripBuffers[planeIndex].GetSpan(),
cancellationToken);
stripIndex += stripsPerPlane;
}
@ -449,7 +454,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
Buffer2D<TPixel> pixels = frame.PixelBuffer;
using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create(
this.Configuration,
this.Options,
this.CompressionType,
this.memoryAllocator,
this.PhotometricInterpretation,
@ -463,7 +468,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
this.byteOrder);
TiffBaseColorDecoder<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.Create(
this.Configuration,
this.configuration,
this.memoryAllocator,
this.ColorType,
this.BitsPerSample,
@ -494,7 +499,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff
stripOffsets[stripIndex],
stripByteCounts[stripIndex],
stripHeight,
stripBufferSpan);
stripBufferSpan,
cancellationToken);
colorDecoder.Decode(stripBufferSpan, pixels, 0, top, frame.Width, stripHeight);
}

23
src/ImageSharp/Formats/Webp/IWebpDecoderOptions.cs

@ -1,23 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Metadata;
namespace SixLabors.ImageSharp.Formats.Webp
{
/// <summary>
/// Image decoder options for generating an image out of a webp stream.
/// </summary>
internal interface IWebpDecoderOptions
{
/// <summary>
/// Gets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
bool IgnoreMetadata { get; }
/// <summary>
/// Gets the decoding mode for multi-frame images.
/// </summary>
FrameDecodingMode DecodingMode { get; }
}
}

39
src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs

@ -33,6 +33,11 @@ namespace SixLabors.ImageSharp.Formats.Webp
/// </summary>
private readonly Configuration configuration;
/// <summary>
/// The maximum number of frames to decode. Inclusive.
/// </summary>
private readonly uint maxFrames;
/// <summary>
/// The area to restore.
/// </summary>
@ -48,29 +53,24 @@ namespace SixLabors.ImageSharp.Formats.Webp
/// </summary>
private WebpMetadata webpMetadata;
/// <summary>
/// The alpha data, if an ALPH chunk is present.
/// </summary>
private IMemoryOwner<byte> alphaData;
/// <summary>
/// Initializes a new instance of the <see cref="WebpAnimationDecoder"/> class.
/// </summary>
/// <param name="memoryAllocator">The memory allocator.</param>
/// <param name="configuration">The global configuration.</param>
/// <param name="decodingMode">The frame decoding mode.</param>
public WebpAnimationDecoder(MemoryAllocator memoryAllocator, Configuration configuration, FrameDecodingMode decodingMode)
/// <param name="maxFrames">The maximum number of frames to decode. Inclusive.</param>
public WebpAnimationDecoder(MemoryAllocator memoryAllocator, Configuration configuration, uint maxFrames)
{
this.memoryAllocator = memoryAllocator;
this.configuration = configuration;
this.DecodingMode = decodingMode;
this.maxFrames = maxFrames;
}
/// <summary>
/// Gets or sets the alpha data, if an ALPH chunk is present.
/// </summary>
public IMemoryOwner<byte> AlphaData { get; set; }
/// <summary>
/// Gets the decoding mode for multi-frame images.
/// </summary>
public FrameDecodingMode DecodingMode { get; }
/// <summary>
/// Decodes the animated webp image from the specified stream.
/// </summary>
@ -90,6 +90,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
this.webpMetadata = this.metadata.GetWebpMetadata();
this.webpMetadata.AnimationLoopCount = features.AnimationLoopCount;
uint frameCount = 0;
int remainingBytes = (int)completeDataSize;
while (remainingBytes > 0)
{
@ -110,7 +111,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
break;
}
if (stream.Position == stream.Length || this.DecodingMode is FrameDecodingMode.First)
if (stream.Position == stream.Length || ++frameCount == this.maxFrames)
{
break;
}
@ -224,14 +225,14 @@ namespace SixLabors.ImageSharp.Formats.Webp
/// <param name="stream">The stream to read from.</param>
private byte ReadAlphaData(BufferedReadStream stream)
{
this.AlphaData?.Dispose();
this.alphaData?.Dispose();
uint alphaChunkSize = WebpChunkParsingUtils.ReadChunkSize(stream, this.buffer);
int alphaDataSize = (int)(alphaChunkSize - 1);
this.AlphaData = this.memoryAllocator.Allocate<byte>(alphaDataSize);
this.alphaData = this.memoryAllocator.Allocate<byte>(alphaDataSize);
byte alphaChunkHeader = (byte)stream.ReadByte();
Span<byte> alphaData = this.AlphaData.GetSpan();
Span<byte> alphaData = this.alphaData.GetSpan();
stream.Read(alphaData, 0, alphaDataSize);
return alphaChunkHeader;
@ -260,7 +261,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
else
{
var lossyDecoder = new WebpLossyDecoder(webpInfo.Vp8BitReader, this.memoryAllocator, this.configuration);
lossyDecoder.Decode(pixelBufferDecoded, (int)webpInfo.Width, (int)webpInfo.Height, webpInfo, this.AlphaData);
lossyDecoder.Decode(pixelBufferDecoded, (int)webpInfo.Width, (int)webpInfo.Height, webpInfo, this.alphaData);
}
return pixelBufferDecoded;
@ -381,6 +382,6 @@ namespace SixLabors.ImageSharp.Formats.Webp
}
/// <inheritdoc/>
public void Dispose() => this.AlphaData?.Dispose();
public void Dispose() => this.alphaData?.Dispose();
}
}

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

@ -3,8 +3,6 @@
using System.IO;
using System.Threading;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Webp
@ -12,49 +10,34 @@ namespace SixLabors.ImageSharp.Formats.Webp
/// <summary>
/// Image decoder for generating an image out of a webp stream.
/// </summary>
public sealed class WebpDecoder : IImageDecoder, IWebpDecoderOptions, IImageInfoDetector
public sealed class WebpDecoder : IImageDecoder
{
/// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
public bool IgnoreMetadata { get; set; }
/// <summary>
/// Gets or sets the decoding mode for multi-frame images.
/// Defaults to All.
/// </summary>
public FrameDecodingMode DecodingMode { get; set; } = FrameDecodingMode.All;
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));
using var decoder = new WebpDecoderCore(configuration, this);
try
{
return decoder.Decode<TPixel>(configuration, stream, cancellationToken);
}
catch (InvalidMemoryOperationException ex)
{
Size dims = decoder.Dimensions;
throw new InvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex);
}
using WebpDecoderCore decoder = new(options);
return decoder.Identify(options.Configuration, stream, cancellationToken);
}
/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>(configuration, stream, cancellationToken);
/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken)
Image<TPixel> IImageDecoder.Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));
return new WebpDecoderCore(configuration, this).Identify(configuration, stream, cancellationToken);
using WebpDecoderCore decoder = new(options);
Image<TPixel> image = decoder.Decode<TPixel>(options.Configuration, stream, cancellationToken);
ImageDecoderUtilities.Resize(options, image);
return image;
}
/// <inheritdoc/>
Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> ((IImageDecoder)this).Decode<Rgba32>(options, stream, cancellationToken);
}
}

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

@ -4,7 +4,6 @@
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.IO;
using System.Threading;
using SixLabors.ImageSharp.Formats.Webp.Lossless;
using SixLabors.ImageSharp.Formats.Webp.Lossy;
@ -29,65 +28,68 @@ namespace SixLabors.ImageSharp.Formats.Webp
private readonly byte[] buffer = new byte[4];
/// <summary>
/// Used for allocating memory during the decoding operations.
/// General configuration options.
/// </summary>
private readonly MemoryAllocator memoryAllocator;
private readonly Configuration configuration;
/// <summary>
/// The stream to decode from.
/// A value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
private BufferedReadStream currentStream;
private readonly bool skipMetadata;
/// <summary>
/// The webp specific metadata.
/// The maximum number of frames to decode. Inclusive.
/// </summary>
private WebpMetadata webpMetadata;
private readonly uint maxFrames;
/// <summary>
/// Information about the webp image.
/// Gets the <see cref="ImageMetadata"/> decoded by this decoder instance.
/// </summary>
private WebpImageInfo webImageInfo;
private ImageMetadata metadata;
/// <summary>
/// Initializes a new instance of the <see cref="WebpDecoderCore"/> class.
/// Gets or sets the alpha data, if an ALPH chunk is present.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="options">The options.</param>
public WebpDecoderCore(Configuration configuration, IWebpDecoderOptions options)
{
this.Configuration = configuration;
this.DecodingMode = options.DecodingMode;
this.memoryAllocator = configuration.MemoryAllocator;
this.IgnoreMetadata = options.IgnoreMetadata;
}
/// <inheritdoc/>
public Configuration Configuration { get; }
private IMemoryOwner<byte> alphaData;
/// <summary>
/// Gets the decoding mode for multi-frame images.
/// Used for allocating memory during the decoding operations.
/// </summary>
public FrameDecodingMode DecodingMode { get; }
private readonly MemoryAllocator memoryAllocator;
/// <summary>
/// Gets a value indicating whether the metadata should be ignored when the image is being decoded.
/// The stream to decode from.
/// </summary>
public bool IgnoreMetadata { get; }
private BufferedReadStream currentStream;
/// <summary>
/// Gets the <see cref="ImageMetadata"/> decoded by this decoder instance.
/// The webp specific metadata.
/// </summary>
public ImageMetadata Metadata { get; private set; }
private WebpMetadata webpMetadata;
/// <summary>
/// Gets the dimensions of the image.
/// Information about the webp image.
/// </summary>
public Size Dimensions => new((int)this.webImageInfo.Width, (int)this.webImageInfo.Height);
private WebpImageInfo webImageInfo;
/// <summary>
/// Gets or sets the alpha data, if an ALPH chunk is present.
/// Initializes a new instance of the <see cref="WebpDecoderCore"/> class.
/// </summary>
public IMemoryOwner<byte> AlphaData { get; set; }
/// <param name="options">The decoder options.</param>
public WebpDecoderCore(DecoderOptions options)
{
this.Options = options;
this.configuration = options.Configuration;
this.skipMetadata = options.SkipMetadata;
this.maxFrames = options.MaxFrames;
this.memoryAllocator = this.configuration.MemoryAllocator;
}
/// <inheritdoc/>
public DecoderOptions Options { get; }
/// <inheritdoc/>
public Size Dimensions => new((int)this.webImageInfo.Width, (int)this.webImageInfo.Height);
/// <inheritdoc />
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
@ -96,7 +98,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
Image<TPixel> image = null;
try
{
this.Metadata = new ImageMetadata();
this.metadata = new ImageMetadata();
this.currentStream = stream;
uint fileSize = this.ReadImageHeader();
@ -105,7 +107,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
{
if (this.webImageInfo.Features is { Animation: true })
{
using var animationDecoder = new WebpAnimationDecoder(this.memoryAllocator, this.Configuration, this.DecodingMode);
using var animationDecoder = new WebpAnimationDecoder(this.memoryAllocator, this.configuration, this.maxFrames);
return animationDecoder.Decode<TPixel>(stream, this.webImageInfo.Features, this.webImageInfo.Width, this.webImageInfo.Height, fileSize);
}
@ -114,17 +116,17 @@ namespace SixLabors.ImageSharp.Formats.Webp
WebpThrowHelper.ThrowNotSupportedException("Animations are not supported");
}
image = new Image<TPixel>(this.Configuration, (int)this.webImageInfo.Width, (int)this.webImageInfo.Height, this.Metadata);
image = new Image<TPixel>(this.configuration, (int)this.webImageInfo.Width, (int)this.webImageInfo.Height, this.metadata);
Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer();
if (this.webImageInfo.IsLossless)
{
var losslessDecoder = new WebpLosslessDecoder(this.webImageInfo.Vp8LBitReader, this.memoryAllocator, this.Configuration);
var losslessDecoder = new WebpLosslessDecoder(this.webImageInfo.Vp8LBitReader, this.memoryAllocator, this.configuration);
losslessDecoder.Decode(pixels, image.Width, image.Height);
}
else
{
var lossyDecoder = new WebpLossyDecoder(this.webImageInfo.Vp8BitReader, this.memoryAllocator, this.Configuration);
lossyDecoder.Decode(pixels, image.Width, image.Height, this.webImageInfo, this.AlphaData);
var lossyDecoder = new WebpLossyDecoder(this.webImageInfo.Vp8BitReader, this.memoryAllocator, this.configuration);
lossyDecoder.Decode(pixels, image.Width, image.Height, this.webImageInfo, this.alphaData);
}
// There can be optional chunks after the image data, like EXIF and XMP.
@ -151,7 +153,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
this.ReadImageHeader();
using (this.webImageInfo = this.ReadVp8Info(true))
{
return new ImageInfo(new PixelTypeInfo((int)this.webImageInfo.BitsPerPixel), (int)this.webImageInfo.Width, (int)this.webImageInfo.Height, this.Metadata);
return new ImageInfo(new PixelTypeInfo((int)this.webImageInfo.BitsPerPixel), (int)this.webImageInfo.Width, (int)this.webImageInfo.Height, this.metadata);
}
}
@ -182,8 +184,8 @@ namespace SixLabors.ImageSharp.Formats.Webp
/// <returns>Information about the webp image.</returns>
private WebpImageInfo ReadVp8Info(bool ignoreAlpha = false)
{
this.Metadata = new ImageMetadata();
this.webpMetadata = this.Metadata.GetFormatMetadata(WebpFormat.Instance);
this.metadata = new ImageMetadata();
this.webpMetadata = this.metadata.GetFormatMetadata(WebpFormat.Instance);
WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(this.currentStream, this.buffer);
@ -277,7 +279,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
/// <param name="features">The webp features.</param>
private void ParseOptionalChunks(WebpFeatures features)
{
if (this.IgnoreMetadata || (features.ExifProfile == false && features.XmpMetaData == false))
if (this.skipMetadata || (!features.ExifProfile && !features.XmpMetaData))
{
return;
}
@ -287,11 +289,11 @@ namespace SixLabors.ImageSharp.Formats.Webp
{
// Read chunk header.
WebpChunkType chunkType = this.ReadChunkType();
if (chunkType == WebpChunkType.Exif && this.Metadata.ExifProfile == null)
if (chunkType == WebpChunkType.Exif && this.metadata.ExifProfile == null)
{
this.ReadExifProfile();
}
else if (chunkType == WebpChunkType.Xmp && this.Metadata.XmpProfile == null)
else if (chunkType == WebpChunkType.Xmp && this.metadata.XmpProfile == null)
{
this.ReadXmpProfile();
}
@ -310,7 +312,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
private void ReadExifProfile()
{
uint exifChunkSize = this.ReadChunkSize();
if (this.IgnoreMetadata)
if (this.skipMetadata)
{
this.currentStream.Skip((int)exifChunkSize);
}
@ -325,7 +327,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
}
var profile = new ExifProfile(exifData);
this.Metadata.ExifProfile = profile;
this.metadata.ExifProfile = profile;
}
}
@ -335,7 +337,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
private void ReadXmpProfile()
{
uint xmpChunkSize = this.ReadChunkSize();
if (this.IgnoreMetadata)
if (this.skipMetadata)
{
this.currentStream.Skip((int)xmpChunkSize);
}
@ -350,7 +352,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
}
var profile = new XmpProfile(xmpData);
this.Metadata.XmpProfile = profile;
this.metadata.XmpProfile = profile;
}
}
@ -360,7 +362,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
private void ReadIccProfile()
{
uint iccpChunkSize = this.ReadChunkSize();
if (this.IgnoreMetadata)
if (this.skipMetadata)
{
this.currentStream.Skip((int)iccpChunkSize);
}
@ -376,7 +378,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
var profile = new IccProfile(iccpData);
if (profile.CheckIsValid())
{
this.Metadata.IccProfile = profile;
this.metadata.IccProfile = profile;
}
}
}
@ -419,8 +421,8 @@ namespace SixLabors.ImageSharp.Formats.Webp
features.AlphaChunkHeader = (byte)this.currentStream.ReadByte();
int alphaDataSize = (int)(alphaChunkSize - 1);
this.AlphaData = this.memoryAllocator.Allocate<byte>(alphaDataSize);
Span<byte> alphaData = this.AlphaData.GetSpan();
this.alphaData = this.memoryAllocator.Allocate<byte>(alphaDataSize);
Span<byte> alphaData = this.alphaData.GetSpan();
int bytesRead = this.currentStream.Read(alphaData, 0, alphaDataSize);
if (bytesRead != alphaDataSize)
{
@ -462,6 +464,6 @@ namespace SixLabors.ImageSharp.Formats.Webp
}
/// <inheritdoc/>
public void Dispose() => this.AlphaData?.Dispose();
public void Dispose() => this.alphaData?.Dispose();
}
}

38
src/ImageSharp/Image.Decode.cs

@ -44,14 +44,14 @@ namespace SixLabors.ImageSharp
/// <summary>
/// By reading the header on the provided stream this calculates the images format.
/// </summary>
/// <param name="configuration">The general configuration.</param>
/// <param name="stream">The image stream to read the header from.</param>
/// <param name="config">The configuration.</param>
/// <returns>The mime type or null if none found.</returns>
private static IImageFormat InternalDetectFormat(Stream stream, Configuration config)
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
// to ensure that only formats that headers fit within the given buffer length are tested.
int headerSize = (int)Math.Min(config.MaxHeaderSize, stream.Length);
int headerSize = (int)Math.Min(configuration.MaxHeaderSize, stream.Length);
if (headerSize <= 0)
{
return null;
@ -80,7 +80,7 @@ namespace SixLabors.ImageSharp
// and does that data match the format specification?
// Individual formats should still check since they are public.
IImageFormat format = null;
foreach (IImageFormatDetector formatDetector in config.ImageFormatsManager.FormatDetectors)
foreach (IImageFormatDetector formatDetector in configuration.ImageFormatsManager.FormatDetectors)
{
if (formatDetector.HeaderSize <= headerSize)
{
@ -98,73 +98,73 @@ namespace SixLabors.ImageSharp
/// <summary>
/// By reading the header on the provided stream this calculates the images format.
/// </summary>
/// <param name="options">The general decoder options.</param>
/// <param name="stream">The image stream to read the header from.</param>
/// <param name="config">The configuration.</param>
/// <param name="format">The IImageFormat.</param>
/// <returns>The image format or null if none found.</returns>
private static IImageDecoder DiscoverDecoder(Stream stream, Configuration config, out IImageFormat format)
private static IImageDecoder DiscoverDecoder(DecoderOptions options, Stream stream, out IImageFormat format)
{
format = InternalDetectFormat(stream, config);
format = InternalDetectFormat(options.Configuration, stream);
return format != null
? config.ImageFormatsManager.FindDecoder(format)
? options.Configuration.ImageFormatsManager.FindDecoder(format)
: null;
}
/// <summary>
/// Decodes the image stream to the current image.
/// </summary>
/// <param name="options">The general decoder options.</param>
/// <param name="stream">The stream.</param>
/// <param name="config">the configuration.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>
/// A new <see cref="Image{TPixel}"/>.
/// </returns>
private static (Image<TPixel> Image, IImageFormat Format) Decode<TPixel>(Stream stream, Configuration config, CancellationToken cancellationToken = default)
private static (Image<TPixel> Image, IImageFormat Format) Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken = default)
where TPixel : unmanaged, IPixel<TPixel>
{
IImageDecoder decoder = DiscoverDecoder(stream, config, out IImageFormat format);
IImageDecoder decoder = DiscoverDecoder(options, stream, out IImageFormat format);
if (decoder is null)
{
return (null, null);
}
Image<TPixel> img = decoder.Decode<TPixel>(config, stream, cancellationToken);
Image<TPixel> img = decoder.Decode<TPixel>(options, stream, cancellationToken);
return (img, format);
}
private static (Image Image, IImageFormat Format) Decode(Stream stream, Configuration config, CancellationToken cancellationToken = default)
private static (Image Image, IImageFormat Format) Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken = default)
{
IImageDecoder decoder = DiscoverDecoder(stream, config, out IImageFormat format);
IImageDecoder decoder = DiscoverDecoder(options, stream, out IImageFormat format);
if (decoder is null)
{
return (null, null);
}
Image img = decoder.Decode(config, stream, cancellationToken);
Image img = decoder.Decode(options, stream, cancellationToken);
return (img, format);
}
/// <summary>
/// Reads the raw image information from the specified stream.
/// </summary>
/// <param name="options">The general decoder options.</param>
/// <param name="stream">The stream.</param>
/// <param name="config">the configuration.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>
/// The <see cref="IImageInfo"/> or null if a suitable info detector is not found.
/// </returns>
private static (IImageInfo ImageInfo, IImageFormat Format) InternalIdentity(Stream stream, Configuration config, CancellationToken cancellationToken = default)
private static (IImageInfo ImageInfo, IImageFormat Format) InternalIdentity(DecoderOptions options, Stream stream, CancellationToken cancellationToken = default)
{
IImageDecoder decoder = DiscoverDecoder(stream, config, out IImageFormat format);
IImageDecoder decoder = DiscoverDecoder(options, stream, out IImageFormat format);
if (decoder is not IImageInfoDetector detector)
{
return (null, null);
}
IImageInfo info = detector?.Identify(config, stream, cancellationToken);
IImageInfo info = detector?.Identify(options, stream, cancellationToken);
return (info, format);
}
}

451
src/ImageSharp/Image.FromBytes.cs

@ -9,47 +9,59 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp
{
/// <content>
/// Adds static methods allowing the creation of new image from a byte array.
/// Adds static methods allowing the creation of new image from a byte span.
/// </content>
public abstract partial class Image
{
/// <summary>
/// By reading the header on the provided byte array this calculates the images format.
/// By reading the header on the provided byte span this calculates the images format.
/// </summary>
/// <param name="data">The byte array containing encoded image data to read the header from.</param>
/// <exception cref="ArgumentNullException">The data is null.</exception>
/// <param name="data">The byte span containing encoded image data to read the header from.</param>
/// <returns>The format or null if none found.</returns>
public static IImageFormat DetectFormat(byte[] data)
=> DetectFormat(Configuration.Default, data);
public static IImageFormat DetectFormat(ReadOnlySpan<byte> data)
=> DetectFormat(DecoderOptions.Default, data);
/// <summary>
/// By reading the header on the provided byte array this calculates the images format.
/// By reading the header on the provided byte span this calculates the images format.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="data">The byte array containing encoded image data to read the header from.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The data is null.</exception>
/// <param name="options">The general decoder options.</param>
/// <param name="data">The byte span containing encoded image data to read the header from.</param>
/// <exception cref="ArgumentNullException">The options are null.</exception>
/// <returns>The mime type or null if none found.</returns>
public static IImageFormat DetectFormat(Configuration configuration, byte[] data)
public static IImageFormat DetectFormat(DecoderOptions options, ReadOnlySpan<byte> data)
{
Guard.NotNull(data, nameof(data));
Guard.NotNull(options, nameof(options.Configuration));
using (var stream = new MemoryStream(data, 0, data.Length, false, true))
Configuration configuration = options.Configuration;
int maxHeaderSize = configuration.MaxHeaderSize;
if (maxHeaderSize <= 0)
{
return DetectFormat(configuration, stream);
return null;
}
foreach (IImageFormatDetector detector in configuration.ImageFormatsManager.FormatDetectors)
{
IImageFormat f = detector.DetectFormat(data);
if (f != null)
{
return f;
}
}
return default;
}
/// <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="data">The byte span containing encoded image data to read the header from.</param>
/// <exception cref="ArgumentNullException">The data is null.</exception>
/// <exception cref="NotSupportedException">The data is not readable.</exception>
/// <returns>
/// The <see cref="IImageInfo"/> or null if suitable info detector not found.
/// </returns>
public static IImageInfo Identify(byte[] data) => Identify(data, out IImageFormat _);
public static IImageInfo Identify(ReadOnlySpan<byte> data) => Identify(data, out IImageFormat _);
/// <summary>
/// Reads the raw image information from the specified stream without fully decoding it.
@ -61,13 +73,14 @@ namespace SixLabors.ImageSharp
/// <returns>
/// The <see cref="IImageInfo"/> or null if suitable info detector not found.
/// </returns>
public static IImageInfo Identify(byte[] data, out IImageFormat format) => Identify(Configuration.Default, data, out format);
public static IImageInfo Identify(ReadOnlySpan<byte> data, out IImageFormat format)
=> Identify(DecoderOptions.Default, data, out format);
/// <summary>
/// Reads the raw image information from the specified stream without fully decoding it.
/// Reads the raw image information from the specified span of bytes without fully decoding it.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="data">The byte array containing encoded image data to read the header from.</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="format">The format type of the decoded image.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The data is null.</exception>
@ -75,185 +88,15 @@ namespace SixLabors.ImageSharp
/// <returns>
/// The <see cref="IImageInfo"/> or null if suitable info detector is not found.
/// </returns>
public static IImageInfo Identify(Configuration configuration, byte[] data, out IImageFormat format)
public static unsafe IImageInfo Identify(DecoderOptions options, ReadOnlySpan<byte> data, out IImageFormat format)
{
Guard.NotNull(data, nameof(data));
using (var stream = new MemoryStream(data, 0, data.Length, false, true))
fixed (byte* ptr = data)
{
return Identify(configuration, stream, out format);
using var stream = new UnmanagedMemoryStream(ptr, data.Length);
return Identify(options, stream, out format);
}
}
/// <summary>
/// Load a new instance of <see cref="Image{Rgba32}"/> from the given encoded byte array.
/// </summary>
/// <param name="data">The byte array containing image data.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The data is null.</exception>
/// <returns>A new <see cref="Image"/>.</returns>
public static Image Load(byte[] data)
=> Load(Configuration.Default, data);
/// <summary>
/// Load a new instance of <see cref="Image{TPixel}"/> from the given encoded byte array.
/// </summary>
/// <param name="data">The byte array containing encoded image data.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="ArgumentNullException">The data 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 new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(byte[] data)
where TPixel : unmanaged, IPixel<TPixel>
=> Load<TPixel>(Configuration.Default, data);
/// <summary>
/// Load a new instance of <see cref="Image{TPixel}"/> from the given encoded byte array.
/// </summary>
/// <param name="data">The byte array containing image data.</param>
/// <param name="format">The mime type of the decoded image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="ArgumentNullException">The data 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 new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(byte[] data, out IImageFormat format)
where TPixel : unmanaged, IPixel<TPixel>
=> Load<TPixel>(Configuration.Default, data, out format);
/// <summary>
/// Load a new instance of <see cref="Image{TPixel}"/> from the given encoded byte array.
/// </summary>
/// <param name="configuration">The configuration options.</param>
/// <param name="data">The byte array containing encoded image data.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The data 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 new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Configuration configuration, byte[] data)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(data, nameof(data));
using (var stream = new MemoryStream(data, 0, data.Length, false, true))
{
return Load<TPixel>(configuration, stream);
}
}
/// <summary>
/// Load a new instance of <see cref="Image{TPixel}"/> from the given encoded byte array.
/// </summary>
/// <param name="configuration">The configuration options.</param>
/// <param name="data">The byte array containing encoded image data.</param>
/// <param name="format">The <see cref="IImageFormat"/> of the decoded image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The data 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 new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Configuration configuration, byte[] data, out IImageFormat format)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(data, nameof(data));
using (var stream = new MemoryStream(data, 0, data.Length, false, true))
{
return Load<TPixel>(configuration, stream, out format);
}
}
/// <summary>
/// Load a new instance of <see cref="Image{TPixel}"/> from the given encoded byte array.
/// </summary>
/// <param name="data">The byte array containing encoded image data.</param>
/// <param name="decoder">The decoder.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="ArgumentNullException">The data 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 new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(byte[] data, IImageDecoder decoder)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(data, nameof(data));
using (var stream = new MemoryStream(data, 0, data.Length, false, true))
{
return Load<TPixel>(stream, decoder);
}
}
/// <summary>
/// Load a new instance of <see cref="Image{TPixel}"/> from the given encoded byte array.
/// </summary>
/// <param name="configuration">The Configuration.</param>
/// <param name="data">The byte array containing encoded image data.</param>
/// <param name="decoder">The decoder.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The data 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 new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Configuration configuration, byte[] data, IImageDecoder decoder)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(data, nameof(data));
using (var stream = new MemoryStream(data, 0, data.Length, false, true))
{
return Load<TPixel>(configuration, stream, decoder);
}
}
/// <summary>
/// By reading the header on the provided byte span this calculates the images format.
/// </summary>
/// <param name="data">The byte span containing encoded image data to read the header from.</param>
/// <returns>The format or null if none found.</returns>
public static IImageFormat DetectFormat(ReadOnlySpan<byte> data) => DetectFormat(Configuration.Default, data);
/// <summary>
/// By reading the header on the provided byte span this calculates the images format.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="data">The byte span containing encoded image data to read the header from.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <returns>The mime type or null if none found.</returns>
public static IImageFormat DetectFormat(Configuration configuration, ReadOnlySpan<byte> data)
{
Guard.NotNull(configuration, nameof(configuration));
int maxHeaderSize = configuration.MaxHeaderSize;
if (maxHeaderSize <= 0)
{
return null;
}
foreach (IImageFormatDetector detector in configuration.ImageFormatsManager.FormatDetectors)
{
IImageFormat f = detector.DetectFormat(data);
if (f != null)
{
return f;
}
}
return default;
}
/// <summary>
/// Load a new instance of <see cref="Image{TPixel}"/> from the given encoded byte span.
/// </summary>
@ -265,7 +108,7 @@ namespace SixLabors.ImageSharp
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(ReadOnlySpan<byte> data)
where TPixel : unmanaged, IPixel<TPixel>
=> Load<TPixel>(Configuration.Default, data);
=> Load<TPixel>(DecoderOptions.Default, data);
/// <summary>
/// Load a new instance of <see cref="Image{TPixel}"/> from the given encoded byte span.
@ -279,177 +122,51 @@ namespace SixLabors.ImageSharp
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(ReadOnlySpan<byte> data, out IImageFormat format)
where TPixel : unmanaged, IPixel<TPixel>
=> Load<TPixel>(Configuration.Default, data, out format);
=> Load<TPixel>(DecoderOptions.Default, data, out format);
/// <summary>
/// Load a new instance of <see cref="Image{TPixel}"/> from the given encoded byte span.
/// </summary>
/// <param name="options">The general decoder options.</param>
/// <param name="data">The byte span containing encoded image data.</param>
/// <param name="decoder">The decoder.</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, IImageDecoder decoder)
where TPixel : unmanaged, IPixel<TPixel>
=> Load<TPixel>(Configuration.Default, data, decoder);
/// <summary>
/// Load a new instance of <see cref="Image{TPixel}"/> from the given encoded byte span.
/// </summary>
/// <param name="configuration">The configuration options.</param>
/// <param name="data">The byte span containing encoded image data.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="ArgumentNullException">The configuration 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>A new <see cref="Image{TPixel}"/>.</returns>
public static unsafe Image<TPixel> Load<TPixel>(Configuration configuration, ReadOnlySpan<byte> data)
where TPixel : unmanaged, IPixel<TPixel>
{
fixed (byte* ptr = &data.GetPinnableReference())
{
using (var stream = new UnmanagedMemoryStream(ptr, data.Length))
{
return Load<TPixel>(configuration, stream);
}
}
}
/// <summary>
/// Load a new instance of <see cref="Image{TPixel}"/> from the given encoded byte span.
/// </summary>
/// <param name="configuration">The Configuration.</param>
/// <param name="data">The byte span containing image data.</param>
/// <param name="decoder">The decoder.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The options are 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>A new <see cref="Image{TPixel}"/>.</returns>
public static unsafe Image<TPixel> Load<TPixel>(
Configuration configuration,
ReadOnlySpan<byte> data,
IImageDecoder decoder)
public static unsafe Image<TPixel> Load<TPixel>(DecoderOptions options, ReadOnlySpan<byte> data)
where TPixel : unmanaged, IPixel<TPixel>
{
fixed (byte* ptr = &data.GetPinnableReference())
fixed (byte* ptr = data)
{
using (var stream = new UnmanagedMemoryStream(ptr, data.Length))
{
return Load<TPixel>(configuration, stream, decoder);
}
using var stream = new UnmanagedMemoryStream(ptr, data.Length);
return Load<TPixel>(options, stream);
}
}
/// <summary>
/// Load a new instance of <see cref="Image{TPixel}"/> from the given encoded byte span.
/// </summary>
/// <param name="configuration">The configuration options.</param>
/// <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>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The options are 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>A new <see cref="Image{TPixel}"/>.</returns>
public static unsafe Image<TPixel> Load<TPixel>(
Configuration configuration,
DecoderOptions options,
ReadOnlySpan<byte> data,
out IImageFormat format)
where TPixel : unmanaged, IPixel<TPixel>
{
fixed (byte* ptr = &data.GetPinnableReference())
{
using (var stream = new UnmanagedMemoryStream(ptr, data.Length))
{
return Load<TPixel>(configuration, stream, out format);
}
}
}
/// <summary>
/// Load a new instance of <see cref="Image"/> from the given encoded byte array.
/// </summary>
/// <param name="data">The byte array containing image data.</param>
/// <param name="format">The detected format.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The data 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(byte[] data, out IImageFormat format)
=> Load(Configuration.Default, data, out format);
/// <summary>
/// Load a new instance of <see cref="Image"/> from the given encoded byte array.
/// </summary>
/// <param name="data">The byte array containing encoded image data.</param>
/// <param name="decoder">The decoder.</param>
/// <exception cref="ArgumentNullException">The data 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(byte[] data, IImageDecoder decoder)
=> Load(Configuration.Default, data, decoder);
/// <summary>
/// Load a new instance of <see cref="Image"/> from the given encoded byte array.
/// </summary>
/// <param name="configuration">The configuration for the decoder.</param>
/// <param name="data">The byte array containing encoded image data.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The data 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(Configuration configuration, byte[] data)
=> Load(configuration, data, out _);
/// <summary>
/// Load a new instance of <see cref="Image"/> from the given encoded byte array.
/// </summary>
/// <param name="configuration">The configuration for the decoder.</param>
/// <param name="data">The byte array containing image data.</param>
/// <param name="decoder">The decoder.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The data 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(Configuration configuration, byte[] data, IImageDecoder decoder)
{
using (var stream = new MemoryStream(data, 0, data.Length, false, true))
{
return Load(configuration, stream, decoder);
}
}
/// <summary>
/// Load a new instance of <see cref="Image"/> from the given encoded byte array.
/// </summary>
/// <param name="configuration">The configuration for the decoder.</param>
/// <param name="data">The byte array containing image data.</param>
/// <param name="format">The mime type of the decoded image.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The data 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(Configuration configuration, byte[] data, out IImageFormat format)
{
using (var stream = new MemoryStream(data, 0, data.Length, false, true))
fixed (byte* ptr = data)
{
return Load(configuration, stream, out format);
using var stream = new UnmanagedMemoryStream(ptr, data.Length);
return Load<TPixel>(options, stream, out format);
}
}
@ -462,21 +179,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <returns>The <see cref="Image"/>.</returns>
public static Image Load(ReadOnlySpan<byte> data)
=> Load(Configuration.Default, data);
/// <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>
/// <param name="decoder">The decoder.</param>
/// <exception cref="ArgumentNullException">The data is null.</exception>
/// <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, IImageDecoder decoder)
=> Load(Configuration.Default, data, decoder);
=> Load(DecoderOptions.Default, data);
/// <summary>
/// Load a new instance of <see cref="Image"/> from the given encoded byte array.
@ -489,65 +192,37 @@ namespace SixLabors.ImageSharp
/// <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(Configuration.Default, data, out 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="configuration">The configuration options.</param>
/// <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(Configuration configuration, ReadOnlySpan<byte> data)
=> Load(configuration, data, out _);
public static Image Load(DecoderOptions options, ReadOnlySpan<byte> data)
=> Load(options, data, out _);
/// <summary>
/// Load a new instance of <see cref="Image"/> from the given encoded byte span.
/// </summary>
/// <param name="configuration">The Configuration.</param>
/// <param name="data">The byte span containing image data.</param>
/// <param name="decoder">The decoder.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The decoder 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>The <see cref="Image"/>.</returns>
public static unsafe Image Load(
Configuration configuration,
ReadOnlySpan<byte> data,
IImageDecoder decoder)
{
fixed (byte* ptr = &data.GetPinnableReference())
{
using (var stream = new UnmanagedMemoryStream(ptr, data.Length))
{
return Load(configuration, stream, decoder);
}
}
}
/// <summary>
/// Load a new instance of <see cref="Image"/> from the given encoded byte span.
/// </summary>
/// <param name="configuration">The configuration options.</param>
/// <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>>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The options are 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 unsafe Image Load(
Configuration configuration,
DecoderOptions options,
ReadOnlySpan<byte> data,
out IImageFormat format)
{
fixed (byte* ptr = &data.GetPinnableReference())
fixed (byte* ptr = data)
{
using (var stream = new UnmanagedMemoryStream(ptr, data.Length))
{
return Load(configuration, stream, out format);
}
using var stream = new UnmanagedMemoryStream(ptr, data.Length);
return Load(options, stream, out format);
}
}
}

295
src/ImageSharp/Image.FromFile.cs

@ -21,23 +21,21 @@ namespace SixLabors.ImageSharp
/// <param name="filePath">The image file to open and to read the header from.</param>
/// <returns>The mime type or null if none found.</returns>
public static IImageFormat DetectFormat(string filePath)
=> DetectFormat(Configuration.Default, filePath);
=> DetectFormat(DecoderOptions.Default, filePath);
/// <summary>
/// By reading the header on the provided file this calculates the images mime type.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="options">The general decoder options.</param>
/// <param name="filePath">The image file to open and to read the header from.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <returns>The mime type or null if none found.</returns>
public static IImageFormat DetectFormat(Configuration configuration, string filePath)
public static IImageFormat DetectFormat(DecoderOptions options, string filePath)
{
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(options, nameof(options));
using (Stream file = configuration.FileSystem.OpenRead(filePath))
{
return DetectFormat(configuration, file);
}
using Stream file = options.Configuration.FileSystem.OpenRead(filePath);
return DetectFormat(options, file);
}
/// <summary>
@ -59,25 +57,23 @@ namespace SixLabors.ImageSharp
/// The <see cref="IImageInfo"/> or null if suitable info detector not found.
/// </returns>
public static IImageInfo Identify(string filePath, out IImageFormat format)
=> Identify(Configuration.Default, filePath, out format);
=> Identify(DecoderOptions.Default, filePath, out format);
/// <summary>
/// Reads the raw image information from the specified stream without fully decoding it.
/// </summary>
/// <param name="configuration">The configuration.</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="format">The format type of the decoded image.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <returns>
/// The <see cref="IImageInfo"/> or null if suitable info detector is not found.
/// </returns>
public static IImageInfo Identify(Configuration configuration, string filePath, out IImageFormat format)
public static IImageInfo Identify(DecoderOptions options, string filePath, out IImageFormat format)
{
Guard.NotNull(configuration, nameof(configuration));
using (Stream file = configuration.FileSystem.OpenRead(filePath))
{
return Identify(configuration, file, out format);
}
Guard.NotNull(options, nameof(options));
using Stream file = options.Configuration.FileSystem.OpenRead(filePath);
return Identify(options, file, out format);
}
/// <summary>
@ -91,12 +87,12 @@ namespace SixLabors.ImageSharp
/// <see cref="IImageInfo"/> property set to null if suitable info detector is not found.
/// </returns>
public static Task<IImageInfo> IdentifyAsync(string filePath, CancellationToken cancellationToken = default)
=> IdentifyAsync(Configuration.Default, filePath, cancellationToken);
=> IdentifyAsync(DecoderOptions.Default, filePath, cancellationToken);
/// <summary>
/// Reads the raw image information from the specified stream without fully decoding it.
/// </summary>
/// <param name="configuration">The configuration.</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="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
@ -105,12 +101,12 @@ namespace SixLabors.ImageSharp
/// <see cref="IImageInfo"/> property set to null if suitable info detector is not found.
/// </returns>
public static async Task<IImageInfo> IdentifyAsync(
Configuration configuration,
DecoderOptions options,
string filePath,
CancellationToken cancellationToken = default)
{
(IImageInfo ImageInfo, IImageFormat Format) res = await IdentifyWithFormatAsync(configuration, filePath, cancellationToken)
.ConfigureAwait(false);
(IImageInfo ImageInfo, IImageFormat Format) res =
await IdentifyWithFormatAsync(options, filePath, cancellationToken).ConfigureAwait(false);
return res.ImageInfo;
}
@ -127,12 +123,12 @@ namespace SixLabors.ImageSharp
public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync(
string filePath,
CancellationToken cancellationToken = default)
=> IdentifyWithFormatAsync(Configuration.Default, filePath, cancellationToken);
=> IdentifyWithFormatAsync(DecoderOptions.Default, filePath, cancellationToken);
/// <summary>
/// Reads the raw image information from the specified stream without fully decoding it.
/// </summary>
/// <param name="configuration">The configuration.</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="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
@ -141,13 +137,13 @@ namespace SixLabors.ImageSharp
/// <see cref="IImageInfo"/> property set to null if suitable info detector is not found.
/// </returns>
public static async Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync(
Configuration configuration,
DecoderOptions options,
string filePath,
CancellationToken cancellationToken = default)
{
Guard.NotNull(configuration, nameof(configuration));
using Stream stream = configuration.FileSystem.OpenRead(filePath);
return await IdentifyWithFormatAsync(configuration, stream, cancellationToken)
Guard.NotNull(options, nameof(options));
using Stream stream = options.Configuration.FileSystem.OpenRead(filePath);
return await IdentifyWithFormatAsync(options, stream, cancellationToken)
.ConfigureAwait(false);
}
@ -160,7 +156,7 @@ namespace SixLabors.ImageSharp
/// </exception>
/// <returns>The <see cref="Image"/>.</returns>
public static Image Load(string path)
=> Load(Configuration.Default, path);
=> Load(DecoderOptions.Default, path);
/// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given file.
@ -172,12 +168,12 @@ namespace SixLabors.ImageSharp
/// </exception>
/// <returns>A new <see cref="Image{Rgba32}"/>.</returns>
public static Image Load(string path, out IImageFormat format)
=> Load(Configuration.Default, path, out format);
=> Load(DecoderOptions.Default, path, out format);
/// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given file.
/// </summary>
/// <param name="configuration">The configuration for the decoder.</param>
/// <param name="options">The general decoder options.</param>
/// <param name="path">The file path to the image.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The path is null.</exception>
@ -185,13 +181,13 @@ namespace SixLabors.ImageSharp
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>The <see cref="Image"/>.</returns>
public static Image Load(Configuration configuration, string path)
=> Load(configuration, path, out _);
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="configuration">The configuration for the decoder.</param>
/// <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>
@ -201,40 +197,16 @@ namespace SixLabors.ImageSharp
/// <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(
Configuration configuration,
DecoderOptions options,
string path,
CancellationToken cancellationToken = default)
{
using Stream stream = configuration.FileSystem.OpenRead(path);
(Image img, _) = await LoadWithFormatAsync(configuration, stream, cancellationToken)
using Stream stream = options.Configuration.FileSystem.OpenRead(path);
(Image img, _) = await LoadWithFormatAsync(options, stream, cancellationToken)
.ConfigureAwait(false);
return img;
}
/// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given file.
/// </summary>
/// <param name="configuration">The Configuration.</param>
/// <param name="path">The file path to the image.</param>
/// <param name="decoder">The decoder.</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>The <see cref="Image"/>.</returns>
public static Image Load(Configuration configuration, string path, IImageDecoder decoder)
{
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(path, nameof(path));
using (Stream stream = configuration.FileSystem.OpenRead(path))
{
return Load(configuration, stream, decoder);
}
}
/// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given file.
/// </summary>
@ -248,99 +220,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
public static Task<Image> LoadAsync(string path, CancellationToken cancellationToken = default)
=> LoadAsync(Configuration.Default, path, cancellationToken);
/// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given file.
/// </summary>
/// <param name="path">The file path to the image.</param>
/// <param name="decoder">The decoder.</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>
public static Task<Image> LoadAsync(string path, IImageDecoder decoder, CancellationToken cancellationToken = default)
=> LoadAsync(Configuration.Default, path, decoder, cancellationToken);
/// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given file.
/// </summary>
/// <param name="path">The file path to the image.</param>
/// <param name="decoder">The decoder.</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>
/// <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, IImageDecoder decoder, CancellationToken cancellationToken = default)
where TPixel : unmanaged, IPixel<TPixel>
=> LoadAsync<TPixel>(Configuration.Default, path, decoder, cancellationToken);
/// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given file.
/// </summary>
/// <param name="configuration">The Configuration.</param>
/// <param name="path">The file path to the image.</param>
/// <param name="decoder">The decoder.</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>
public static async Task<Image> LoadAsync(
Configuration configuration,
string path,
IImageDecoder decoder,
CancellationToken cancellationToken = default)
{
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(path, nameof(path));
using Stream stream = configuration.FileSystem.OpenRead(path);
return await LoadAsync(configuration, stream, decoder, cancellationToken)
.ConfigureAwait(false);
}
/// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given file.
/// </summary>
/// <param name="configuration">The Configuration.</param>
/// <param name="path">The file path to the image.</param>
/// <param name="decoder">The decoder.</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>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
public static async Task<Image<TPixel>> LoadAsync<TPixel>(
Configuration configuration,
string path,
IImageDecoder decoder,
CancellationToken cancellationToken = default)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(path, nameof(path));
using Stream stream = configuration.FileSystem.OpenRead(path);
return await LoadAsync<TPixel>(configuration, stream, decoder, cancellationToken)
.ConfigureAwait(false);
}
=> LoadAsync(DecoderOptions.Default, path, cancellationToken);
/// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given file.
@ -356,12 +236,12 @@ namespace SixLabors.ImageSharp
/// <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>(Configuration.Default, path, cancellationToken);
=> LoadAsync<TPixel>(DecoderOptions.Default, path, cancellationToken);
/// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given file.
/// </summary>
/// <param name="configuration">The configuration for the decoder.</param>
/// <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>
@ -372,31 +252,19 @@ namespace SixLabors.ImageSharp
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
public static async Task<Image<TPixel>> LoadAsync<TPixel>(
Configuration configuration,
DecoderOptions options,
string path,
CancellationToken cancellationToken = default)
where TPixel : unmanaged, IPixel<TPixel>
{
using Stream stream = configuration.FileSystem.OpenRead(path);
(Image<TPixel> img, _) = await LoadWithFormatAsync<TPixel>(configuration, stream, cancellationToken)
.ConfigureAwait(false);
Guard.NotNull(options, nameof(options));
using Stream stream = options.Configuration.FileSystem.OpenRead(path);
(Image<TPixel> img, _) =
await LoadWithFormatAsync<TPixel>(options, stream, cancellationToken).ConfigureAwait(false);
return img;
}
/// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given file.
/// </summary>
/// <param name="path">The file path to the image.</param>
/// <param name="decoder">The decoder.</param>
/// <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>The <see cref="Image"/>.</returns>
public static Image Load(string path, IImageDecoder decoder)
=> Load(Configuration.Default, path, decoder);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file.
/// </summary>
@ -409,7 +277,7 @@ namespace SixLabors.ImageSharp
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(string path)
where TPixel : unmanaged, IPixel<TPixel>
=> Load<TPixel>(Configuration.Default, path);
=> Load<TPixel>(DecoderOptions.Default, path);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file.
@ -424,12 +292,12 @@ namespace SixLabors.ImageSharp
/// <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>(Configuration.Default, path, out format);
=> 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="configuration">The configuration options.</param>
/// <param name="options">The general decoder options.</param>
/// <param name="path">The file path to the image.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The path is null.</exception>
@ -438,22 +306,20 @@ namespace SixLabors.ImageSharp
/// <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>(Configuration configuration, string path)
public static Image<TPixel> Load<TPixel>(DecoderOptions options, string path)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(options, nameof(options));
Guard.NotNull(path, nameof(path));
using (Stream stream = configuration.FileSystem.OpenRead(path))
{
return Load<TPixel>(configuration, stream);
}
using Stream stream = options.Configuration.FileSystem.OpenRead(path);
return Load<TPixel>(options, stream);
}
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file.
/// </summary>
/// <param name="configuration">The configuration options.</param>
/// <param name="options">The general decoder options.</param>
/// <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 configuration is null.</exception>
@ -463,23 +329,21 @@ namespace SixLabors.ImageSharp
/// <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>(Configuration configuration, string path, out IImageFormat format)
public static Image<TPixel> Load<TPixel>(DecoderOptions options, string path, out IImageFormat format)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(options, nameof(options));
Guard.NotNull(path, nameof(path));
using (Stream stream = configuration.FileSystem.OpenRead(path))
{
return Load<TPixel>(configuration, stream, out format);
}
using Stream stream = options.Configuration.FileSystem.OpenRead(path);
return Load<TPixel>(options, stream, out format);
}
/// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given file.
/// The pixel type is selected by the decoder.
/// </summary>
/// <param name="configuration">The configuration options.</param>
/// <param name="options">The general decoder options.</param>
/// <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 configuration is null.</exception>
@ -488,56 +352,13 @@ namespace SixLabors.ImageSharp
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image Load(Configuration configuration, string path, out IImageFormat format)
{
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(path, nameof(path));
using (Stream stream = configuration.FileSystem.OpenRead(path))
{
return Load(configuration, stream, out format);
}
}
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file.
/// </summary>
/// <param name="path">The file path to the image.</param>
/// <param name="decoder">The decoder.</param>
/// <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 new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(string path, IImageDecoder decoder)
where TPixel : unmanaged, IPixel<TPixel>
=> Load<TPixel>(Configuration.Default, path, decoder);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file.
/// </summary>
/// <param name="configuration">The Configuration.</param>
/// <param name="path">The file path to the image.</param>
/// <param name="decoder">The decoder.</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>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Configuration configuration, string path, IImageDecoder decoder)
where TPixel : unmanaged, IPixel<TPixel>
public static Image Load(DecoderOptions options, string path, out IImageFormat format)
{
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(options, nameof(options));
Guard.NotNull(path, nameof(path));
using (Stream stream = configuration.FileSystem.OpenRead(path))
{
return Load<TPixel>(configuration, stream, decoder);
}
using Stream stream = options.Configuration.FileSystem.OpenRead(path);
return Load(options, stream, out format);
}
}
}

407
src/ImageSharp/Image.FromStream.cs

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Text;
using System.Threading;
@ -26,19 +27,19 @@ namespace SixLabors.ImageSharp
/// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <returns>The format type or null if none found.</returns>
public static IImageFormat DetectFormat(Stream stream)
=> DetectFormat(Configuration.Default, stream);
=> DetectFormat(DecoderOptions.Default, stream);
/// <summary>
/// By reading the header on the provided stream this calculates the images format type.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="options">The general decoder options.</param>
/// <param name="stream">The image stream to read the header from.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <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>
/// <returns>The format type or null if none found.</returns>
public static IImageFormat DetectFormat(Configuration configuration, Stream stream)
=> WithSeekableStream(configuration, stream, s => InternalDetectFormat(s, configuration));
public static IImageFormat DetectFormat(DecoderOptions options, Stream stream)
=> WithSeekableStream(options, stream, s => InternalDetectFormat(options.Configuration, s));
/// <summary>
/// By reading the header on the provided stream this calculates the images format type.
@ -49,23 +50,23 @@ namespace SixLabors.ImageSharp
/// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <returns>A <see cref="Task{IImageFormat}"/> representing the asynchronous operation or null if none is found.</returns>
public static Task<IImageFormat> DetectFormatAsync(Stream stream, CancellationToken cancellationToken = default)
=> DetectFormatAsync(Configuration.Default, stream, cancellationToken);
=> DetectFormatAsync(DecoderOptions.Default, stream, cancellationToken);
/// <summary>
/// By reading the header on the provided stream this calculates the images format type.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="options">The general decoder options.</param>
/// <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 configuration is null.</exception>
/// <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>
/// <returns>A <see cref="Task{IImageFormat}"/> representing the asynchronous operation.</returns>
public static Task<IImageFormat> DetectFormatAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken = default)
public static Task<IImageFormat> DetectFormatAsync(DecoderOptions options, Stream stream, CancellationToken cancellationToken = default)
=> WithSeekableStreamAsync(
configuration,
options,
stream,
(s, _) => InternalDetectFormat(s, configuration),
(s, _) => InternalDetectFormat(options.Configuration, s),
cancellationToken);
/// <summary>
@ -94,7 +95,7 @@ namespace SixLabors.ImageSharp
/// a suitable detector is not found.
/// </returns>
public static Task<IImageInfo> IdentifyAsync(Stream stream, CancellationToken cancellationToken = default)
=> IdentifyAsync(Configuration.Default, stream, cancellationToken);
=> IdentifyAsync(DecoderOptions.Default, stream, cancellationToken);
/// <summary>
/// Reads the raw image information from the specified stream without fully decoding it.
@ -108,30 +109,30 @@ namespace SixLabors.ImageSharp
/// 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(Configuration.Default, stream, out format);
=> Identify(DecoderOptions.Default, stream, out format);
/// <summary>
/// Reads the raw image information from the specified stream without fully decoding it.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="options">The general decoder options.</param>
/// <param name="stream">The image stream to read the information from.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <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(Configuration configuration, Stream stream)
=> Identify(configuration, stream, out _);
public static IImageInfo Identify(DecoderOptions options, Stream stream)
=> Identify(options, stream, out _);
/// <summary>
/// Reads the raw image information from the specified stream without fully decoding it.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <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 configuration is null.</exception>
/// <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>
@ -140,30 +141,30 @@ namespace SixLabors.ImageSharp
/// a suitable detector is not found.
/// </returns>
public static async Task<IImageInfo> IdentifyAsync(
Configuration configuration,
DecoderOptions options,
Stream stream,
CancellationToken cancellationToken = default)
{
(IImageInfo ImageInfo, IImageFormat Format) res = await IdentifyWithFormatAsync(configuration, stream, cancellationToken).ConfigureAwait(false);
(IImageInfo ImageInfo, IImageFormat Format) res = await IdentifyWithFormatAsync(options, stream, cancellationToken).ConfigureAwait(false);
return res.ImageInfo;
}
/// <summary>
/// Reads the raw image information from the specified stream without fully decoding it.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="options">The general decoder options.</param>
/// <param name="stream">The image stream to read the information from.</param>
/// <param name="format">The format type of the decoded image.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <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(Configuration configuration, Stream stream, out IImageFormat format)
public static IImageInfo Identify(DecoderOptions options, Stream stream, out IImageFormat format)
{
(IImageInfo ImageInfo, IImageFormat Format) data = WithSeekableStream(configuration, stream, s => InternalIdentity(s, configuration ?? Configuration.Default));
(IImageInfo ImageInfo, IImageFormat Format) data = WithSeekableStream(options, stream, s => InternalIdentity(options, s));
format = data.Format;
return data.ImageInfo;
@ -174,7 +175,7 @@ namespace SixLabors.ImageSharp
/// </summary>
/// <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 configuration is null.</exception>
/// <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>
@ -185,15 +186,15 @@ namespace SixLabors.ImageSharp
public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync(
Stream stream,
CancellationToken cancellationToken = default)
=> IdentifyWithFormatAsync(Configuration.Default, stream, cancellationToken);
=> IdentifyWithFormatAsync(DecoderOptions.Default, stream, cancellationToken);
/// <summary>
/// Reads the raw image information from the specified stream without fully decoding it.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <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 configuration is null.</exception>
/// <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>
@ -202,13 +203,13 @@ namespace SixLabors.ImageSharp
/// <see cref="IImageInfo"/> property set to null if suitable info detector is not found.
/// </returns>
public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync(
Configuration configuration,
DecoderOptions options,
Stream stream,
CancellationToken cancellationToken = default)
=> WithSeekableStreamAsync(
configuration,
options,
stream,
(s, ct) => InternalIdentity(s, configuration ?? Configuration.Default, ct),
(s, ct) => InternalIdentity(options, s, ct),
cancellationToken);
/// <summary>
@ -223,7 +224,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>The <see cref="Image"/>.</returns>
public static Image Load(Stream stream, out IImageFormat format)
=> Load(Configuration.Default, stream, out format);
=> Load(DecoderOptions.Default, stream, out format);
/// <summary>
/// Decode a new instance of the <see cref="Image"/> class from the given stream.
@ -237,7 +238,7 @@ namespace SixLabors.ImageSharp
/// <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(Configuration.Default, stream, cancellationToken);
=> LoadWithFormatAsync(DecoderOptions.Default, stream, cancellationToken);
/// <summary>
/// Decode a new instance of the <see cref="Image"/> class from the given stream.
@ -249,7 +250,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>The <see cref="Image"/>.</returns>
public static Image Load(Stream stream) => Load(Configuration.Default, stream);
public static Image Load(Stream stream) => Load(DecoderOptions.Default, stream);
/// <summary>
/// Decode a new instance of the <see cref="Image"/> class from the given stream.
@ -263,116 +264,37 @@ namespace SixLabors.ImageSharp
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
public static Task<Image> LoadAsync(Stream stream, CancellationToken cancellationToken = default)
=> LoadAsync(Configuration.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>
/// <param name="stream">The stream containing image information.</param>
/// <param name="decoder">The decoder.</param>
/// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="ArgumentNullException">The decoder 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>The <see cref="Image"/>.</returns>
public static Image Load(Stream stream, IImageDecoder decoder)
=> Load(Configuration.Default, stream, decoder);
/// <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="stream">The stream containing image information.</param>
/// <param name="decoder">The decoder.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="ArgumentNullException">The decoder 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{Image}"/> representing the asynchronous operation.</returns>
public static Task<Image> LoadAsync(Stream stream, IImageDecoder decoder, CancellationToken cancellationToken = default)
=> LoadAsync(Configuration.Default, stream, decoder, 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>
/// <param name="configuration">The configuration for the decoder.</param>
/// <param name="stream">The stream containing image information.</param>
/// <param name="decoder">The decoder.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="ArgumentNullException">The decoder 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(Configuration configuration, Stream stream, IImageDecoder decoder)
{
Guard.NotNull(decoder, nameof(decoder));
return WithSeekableStream(configuration, stream, s => decoder.Decode(configuration, s, default));
}
/// <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="configuration">The configuration for the decoder.</param>
/// <param name="stream">The stream containing image information.</param>
/// <param name="decoder">The decoder.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="ArgumentNullException">The decoder 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{Image}"/> representing the asynchronous operation.</returns>
public static Task<Image> LoadAsync(
Configuration configuration,
Stream stream,
IImageDecoder decoder,
CancellationToken cancellationToken = default)
{
Guard.NotNull(decoder, nameof(decoder));
return WithSeekableStreamAsync(
configuration,
stream,
(s, ct) => decoder.Decode(configuration, s, ct),
cancellationToken);
}
=> LoadAsync(DecoderOptions.Default, stream, cancellationToken);
/// <summary>
/// Decode a new instance of the <see cref="Image"/> class from the given stream.
/// </summary>
/// <param name="configuration">The configuration for the decoder.</param>
/// <param name="options">The general decoder options.</param>
/// <param name="stream">The stream containing image information.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <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(Configuration configuration, Stream stream) => Load(configuration, stream, out _);
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>
/// <param name="configuration">The configuration for the decoder.</param>
/// <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 configuration is null.</exception>
/// <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 <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
public static async Task<Image> LoadAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken = default)
public static async Task<Image> LoadAsync(DecoderOptions options, Stream stream, CancellationToken cancellationToken = default)
{
(Image Image, IImageFormat Format) fmt = await LoadWithFormatAsync(configuration, stream, cancellationToken)
(Image Image, IImageFormat Format) fmt = await LoadWithFormatAsync(options, stream, cancellationToken)
.ConfigureAwait(false);
return fmt.Image;
}
@ -389,7 +311,7 @@ namespace SixLabors.ImageSharp
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
=> Load<TPixel>(Configuration.Default, stream);
=> Load<TPixel>(DecoderOptions.Default, stream);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
@ -404,7 +326,7 @@ namespace SixLabors.ImageSharp
/// <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>(Configuration.Default, stream, cancellationToken);
=> LoadAsync<TPixel>(DecoderOptions.Default, stream, cancellationToken);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
@ -419,7 +341,7 @@ namespace SixLabors.ImageSharp
/// <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>(Configuration.Default, stream, out format);
=> Load<TPixel>(DecoderOptions.Default, stream, out format);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
@ -434,185 +356,88 @@ namespace SixLabors.ImageSharp
/// <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>(Configuration.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="decoder">The decoder.</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, IImageDecoder decoder)
where TPixel : unmanaged, IPixel<TPixel>
=> WithSeekableStream(Configuration.Default, stream, s => decoder.Decode<TPixel>(Configuration.Default, s, default));
/// <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="decoder">The decoder.</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"/> representing the asynchronous operation.</returns>
public static Task<Image<TPixel>> LoadAsync<TPixel>(Stream stream, IImageDecoder decoder, CancellationToken cancellationToken = default)
where TPixel : unmanaged, IPixel<TPixel>
=> WithSeekableStreamAsync(
Configuration.Default,
stream,
(s, ct) => decoder.Decode<TPixel>(Configuration.Default, s, ct),
cancellationToken);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary>
/// <param name="configuration">The Configuration.</param>
/// <param name="stream">The stream containing image information.</param>
/// <param name="decoder">The decoder.</param>
/// <exception cref="ArgumentNullException">The configuration 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="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>(Configuration configuration, Stream stream, IImageDecoder decoder)
where TPixel : unmanaged, IPixel<TPixel>
=> WithSeekableStream(configuration, stream, s => decoder.Decode<TPixel>(configuration, s, default));
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary>
/// <param name="configuration">The Configuration.</param>
/// <param name="stream">The stream containing image information.</param>
/// <param name="decoder">The decoder.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">The configuration 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="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"/> representing the asynchronous operation.</returns>
public static Task<Image<TPixel>> LoadAsync<TPixel>(
Configuration configuration,
Stream stream,
IImageDecoder decoder,
CancellationToken cancellationToken = default)
where TPixel : unmanaged, IPixel<TPixel>
=> WithSeekableStreamAsync(
configuration,
stream,
(s, ct) => decoder.Decode<TPixel>(configuration, s, ct),
cancellationToken);
=> 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="configuration">The configuration options.</param>
/// <param name="options">The general decoder options.</param>
/// <param name="stream">The stream containing image information.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <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>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Configuration configuration, Stream stream)
public static Image<TPixel> Load<TPixel>(DecoderOptions options, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
=> Load<TPixel>(configuration, stream, out IImageFormat _);
=> Load<TPixel>(options, stream, out IImageFormat _);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary>
/// <param name="configuration">The configuration options.</param>
/// <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 configuration is null.</exception>
/// <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>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Image<TPixel> Load<TPixel>(Configuration configuration, Stream stream, out IImageFormat format)
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(configuration, stream, s => Decode<TPixel>(s, configuration));
(Image<TPixel> Image, IImageFormat Format) data = WithSeekableStream(options, stream, s => Decode<TPixel>(options, s));
format = data.Format;
if (data.Image != null)
{
return data.Image;
}
var sb = new StringBuilder();
sb.AppendLine("Image cannot be loaded. Available decoders:");
foreach (KeyValuePair<IImageFormat, IImageDecoder> val in configuration.ImageFormatsManager.ImageDecoders)
if (data.Image is null)
{
sb.AppendFormat(" - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine);
ThrowNotLoaded(options);
}
throw new UnknownImageFormatException(sb.ToString());
return data.Image;
}
/// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given stream.
/// </summary>
/// <param name="configuration">The configuration options.</param>
/// <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 configuration is null.</exception>
/// <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 <see cref="Task{ValueTuple}"/> representing the asynchronous operation.</returns>
public static async Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync(
Configuration configuration,
DecoderOptions options,
Stream stream,
CancellationToken cancellationToken = default)
{
(Image Image, IImageFormat Format) data = await WithSeekableStreamAsync(
configuration,
stream,
(s, ct) => Decode(s, configuration, ct),
cancellationToken)
.ConfigureAwait(false);
if (data.Image != null)
{
return data;
}
var sb = new StringBuilder();
sb.AppendLine("Image cannot be loaded. Available decoders:");
(Image Image, IImageFormat Format) data =
await WithSeekableStreamAsync(options, stream, (s, ct) => Decode(options, s, ct), cancellationToken)
.ConfigureAwait(false);
foreach (KeyValuePair<IImageFormat, IImageDecoder> val in configuration.ImageFormatsManager.ImageDecoders)
if (data.Image is null)
{
sb.AppendFormat(" - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine);
ThrowNotLoaded(options);
}
throw new UnknownImageFormatException(sb.ToString());
return data;
}
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary>
/// <param name="configuration">The configuration options.</param>
/// <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 configuration is null.</exception>
/// <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>
@ -620,42 +445,30 @@ namespace SixLabors.ImageSharp
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A <see cref="Task{ValueTuple}"/> representing the asynchronous operation.</returns>
public static async Task<(Image<TPixel> Image, IImageFormat Format)> LoadWithFormatAsync<TPixel>(
Configuration configuration,
DecoderOptions options,
Stream stream,
CancellationToken cancellationToken = default)
where TPixel : unmanaged, IPixel<TPixel>
{
(Image<TPixel> Image, IImageFormat Format) data =
await WithSeekableStreamAsync(
configuration,
stream,
(s, ct) => Decode<TPixel>(s, configuration, ct),
cancellationToken)
.ConfigureAwait(false);
await WithSeekableStreamAsync(options, stream, (s, ct) => Decode<TPixel>(options, s, ct), cancellationToken)
.ConfigureAwait(false);
if (data.Image != null)
if (data.Image is null)
{
return data;
ThrowNotLoaded(options);
}
var sb = new StringBuilder();
sb.AppendLine("Image cannot be loaded. Available decoders:");
foreach (KeyValuePair<IImageFormat, IImageDecoder> val in configuration.ImageFormatsManager.ImageDecoders)
{
sb.AppendFormat(" - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine);
}
throw new UnknownImageFormatException(sb.ToString());
return data;
}
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary>
/// <param name="configuration">The configuration options.</param>
/// <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 configuration is null.</exception>
/// <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>
@ -663,13 +476,13 @@ namespace SixLabors.ImageSharp
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static async Task<Image<TPixel>> LoadAsync<TPixel>(
Configuration configuration,
DecoderOptions options,
Stream stream,
CancellationToken cancellationToken = default)
where TPixel : unmanaged, IPixel<TPixel>
{
(Image<TPixel> img, _) = await LoadWithFormatAsync<TPixel>(configuration, stream, cancellationToken)
.ConfigureAwait(false);
(Image<TPixel> img, _) = await LoadWithFormatAsync<TPixel>(options, stream, cancellationToken)
.ConfigureAwait(false);
return img;
}
@ -677,51 +490,43 @@ namespace SixLabors.ImageSharp
/// 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="configuration">The configuration options.</param>
/// <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 configuration is null.</exception>
/// <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(Configuration configuration, Stream stream, out IImageFormat format)
public static Image Load(DecoderOptions options, Stream stream, out IImageFormat format)
{
(Image Img, IImageFormat Format) data = WithSeekableStream(configuration, stream, s => Decode(s, configuration));
format = data.Format;
(Image img, IImageFormat fmt) = WithSeekableStream(options, stream, s => Decode(options, s));
if (data.Img != null)
{
return data.Img;
}
var sb = new StringBuilder();
sb.AppendLine("Image cannot be loaded. Available decoders:");
format = fmt;
foreach (KeyValuePair<IImageFormat, IImageDecoder> val in configuration.ImageFormatsManager.ImageDecoders)
if (img is null)
{
sb.AppendFormat(" - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine);
ThrowNotLoaded(options);
}
throw new UnknownImageFormatException(sb.ToString());
return img;
}
/// <summary>
/// Performs the given action against the stream ensuring that it is seekable.
/// </summary>
/// <typeparam name="T">The type of object returned from the action.</typeparam>
/// <param name="configuration">The configuration.</param>
/// <param name="options">The general decoder options.</param>
/// <param name="stream">The input stream.</param>
/// <param name="action">The action to perform.</param>
/// <returns>The <typeparamref name="T"/>.</returns>
private static T WithSeekableStream<T>(
Configuration configuration,
internal static T WithSeekableStream<T>(
DecoderOptions options,
Stream stream,
Func<Stream, T> action)
{
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));
if (!stream.CanRead)
@ -729,6 +534,7 @@ namespace SixLabors.ImageSharp
throw new NotSupportedException("Cannot read from the stream.");
}
Configuration configuration = options.Configuration;
if (stream.CanSeek)
{
if (configuration.ReadOrigin == ReadOrigin.Begin)
@ -751,18 +557,18 @@ namespace SixLabors.ImageSharp
/// Performs the given action asynchronously against the stream ensuring that it is seekable.
/// </summary>
/// <typeparam name="T">The type of object returned from the action.</typeparam>
/// <param name="configuration">The configuration.</param>
/// <param name="options">The general decoder options.</param>
/// <param name="stream">The input stream.</param>
/// <param name="action">The action to perform.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The <see cref="Task{T}"/>.</returns>
private static async Task<T> WithSeekableStreamAsync<T>(
Configuration configuration,
internal static async Task<T> WithSeekableStreamAsync<T>(
DecoderOptions options,
Stream stream,
Func<Stream, CancellationToken, T> action,
CancellationToken cancellationToken)
{
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));
if (!stream.CanRead)
@ -770,6 +576,7 @@ namespace SixLabors.ImageSharp
throw new NotSupportedException("Cannot read from the stream.");
}
Configuration configuration = options.Configuration;
if (stream.CanSeek)
{
if (configuration.ReadOrigin == ReadOrigin.Begin)
@ -788,5 +595,19 @@ namespace SixLabors.ImageSharp
return action(memoryStream, cancellationToken);
}
[DoesNotReturn]
private static void ThrowNotLoaded(DecoderOptions options)
{
var sb = new StringBuilder();
sb.AppendLine("Image cannot be loaded. Available decoders:");
foreach (KeyValuePair<IImageFormat, IImageDecoder> val in options.Configuration.ImageFormatsManager.ImageDecoders)
{
sb.AppendFormat(" - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine);
}
throw new UnknownImageFormatException(sb.ToString());
}
}
}

68
src/ImageSharp/Image.LoadPixelData.cs

@ -16,20 +16,7 @@ namespace SixLabors.ImageSharp
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the raw <typeparamref name="TPixel"/> data.
/// </summary>
/// <param name="data">The byte array containing image data.</param>
/// <param name="width">The width of the final image.</param>
/// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="ArgumentException">The data length is incorrect.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> LoadPixelData<TPixel>(TPixel[] data, int width, int height)
where TPixel : unmanaged, IPixel<TPixel>
=> LoadPixelData(Configuration.Default, data, width, height);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the raw <typeparamref name="TPixel"/> data.
/// </summary>
/// <param name="data">The byte array containing image data.</param>
/// <param name="data">The readonly span of bytes containing image data.</param>
/// <param name="width">The width of the final image.</param>
/// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
@ -40,22 +27,9 @@ namespace SixLabors.ImageSharp
=> LoadPixelData(Configuration.Default, data, width, height);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array in <typeparamref name="TPixel"/> format.
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given readonly span of bytes in <typeparamref name="TPixel"/> format.
/// </summary>
/// <param name="data">The byte array containing image data.</param>
/// <param name="width">The width of the final image.</param>
/// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="ArgumentException">The data length is incorrect.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> LoadPixelData<TPixel>(byte[] data, int width, int height)
where TPixel : unmanaged, IPixel<TPixel>
=> LoadPixelData<TPixel>(Configuration.Default, data, width, height);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array in <typeparamref name="TPixel"/> format.
/// </summary>
/// <param name="data">The byte array containing image data.</param>
/// <param name="data">The readonly span of bytes containing image data.</param>
/// <param name="width">The width of the final image.</param>
/// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
@ -66,25 +40,10 @@ namespace SixLabors.ImageSharp
=> LoadPixelData<TPixel>(Configuration.Default, data, width, height);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array in <typeparamref name="TPixel"/> format.
/// </summary>
/// <param name="configuration">The configuration for the decoder.</param>
/// <param name="data">The byte array containing image data.</param>
/// <param name="width">The width of the final image.</param>
/// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentException">The data length is incorrect.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> LoadPixelData<TPixel>(Configuration configuration, byte[] data, int width, int height)
where TPixel : unmanaged, IPixel<TPixel>
=> LoadPixelData(configuration, MemoryMarshal.Cast<byte, TPixel>(new ReadOnlySpan<byte>(data)), width, height);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array in <typeparamref name="TPixel"/> format.
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given readonly span of bytes in <typeparamref name="TPixel"/> format.
/// </summary>
/// <param name="configuration">The configuration for the decoder.</param>
/// <param name="data">The byte array containing image data.</param>
/// <param name="data">The readonly span of bytes containing image data.</param>
/// <param name="width">The width of the final image.</param>
/// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
@ -99,22 +58,7 @@ namespace SixLabors.ImageSharp
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the raw <typeparamref name="TPixel"/> data.
/// </summary>
/// <param name="configuration">The configuration for the decoder.</param>
/// <param name="data">The Span containing the image Pixel data.</param>
/// <param name="width">The width of the final image.</param>
/// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentException">The data length is incorrect.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> LoadPixelData<TPixel>(Configuration configuration, TPixel[] data, int width, int height)
where TPixel : unmanaged, IPixel<TPixel>
=> LoadPixelData(configuration, new ReadOnlySpan<TPixel>(data), width, height);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the raw <typeparamref name="TPixel"/> data.
/// </summary>
/// <param name="configuration">The configuration for the decoder.</param>
/// <param name="data">The Span containing the image Pixel data.</param>
/// <param name="data">The readonly span containing the image pixel data.</param>
/// <param name="width">The width of the final image.</param>
/// <param name="height">The height of the final image.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>

4
tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs

@ -3,6 +3,7 @@
using System.IO;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Tests;
@ -24,7 +25,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
private void GenericBechmark()
{
this.preloadedImageStream.Position = 0;
using Image img = this.decoder.Decode(Configuration.Default, this.preloadedImageStream, default);
using Image img = this.decoder.Decode(DecoderOptions.Default, this.preloadedImageStream);
}
[GlobalSetup(Target = nameof(JpegBaselineInterleaved444))]
@ -64,7 +65,6 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
}
}
/*
BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19042.1348 (20H2/October2020Update)
Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores

4
tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs

@ -38,8 +38,10 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
using var memoryStream = new MemoryStream(this.jpegBytes);
using var bufferedStream = new BufferedReadStream(Configuration.Default, memoryStream);
var options = new JpegDecoderOptions();
options.GeneralOptions.SkipMetadata = true;
using var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder { IgnoreMetadata = true });
using var decoder = new JpegDecoderCore(options);
var spectralConverter = new NoopSpectralConverter();
decoder.ParseStream(bufferedStream, spectralConverter, cancellationToken: default);
}

21
tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_Aggregate.cs

@ -3,7 +3,6 @@
using System.Collections.Generic;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests;
using SDImage = System.Drawing.Image;
@ -18,22 +17,22 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
[Config(typeof(Config.ShortMultiFramework))]
public class DecodeJpeg_Aggregate : MultiImageBenchmarkBase
{
protected override IEnumerable<string> InputImageSubfoldersOrFiles =>
new[]
{
TestImages.Jpeg.BenchmarkSuite.Jpeg400_SmallMonochrome,
TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr,
TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr,
TestImages.Jpeg.BenchmarkSuite.MissingFF00ProgressiveBedroom159_MidSize420YCbCr,
TestImages.Jpeg.BenchmarkSuite.ExifGetString750Transform_Huge420YCbCr,
};
protected override IEnumerable<string> InputImageSubfoldersOrFiles
=> new[]
{
TestImages.Jpeg.BenchmarkSuite.Jpeg400_SmallMonochrome,
TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr,
TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr,
TestImages.Jpeg.BenchmarkSuite.MissingFF00ProgressiveBedroom159_MidSize420YCbCr,
TestImages.Jpeg.BenchmarkSuite.ExifGetString750Transform_Huge420YCbCr,
};
[Params(InputImageCategory.AllImages)]
public override InputImageCategory InputCategory { get; set; }
[Benchmark]
public void ImageSharp()
=> this.ForEachStream(ms => Image.Load<Rgba32>(ms, new JpegDecoder()));
=> this.ForEachStream(ms => Image.Load<Rgba32>(ms));
[Benchmark(Baseline = true)]
public void SystemDrawing()

23
tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs

@ -3,8 +3,7 @@
using System.IO;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Tests;
using SDImage = System.Drawing.Image;
using SDSize = System.Drawing.Size;
@ -47,25 +46,17 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
[Benchmark(Baseline = true)]
public SDSize SystemDrawing()
{
using (var memoryStream = new MemoryStream(this.jpegBytes))
{
using (var image = SDImage.FromStream(memoryStream))
{
return image.Size;
}
}
using var memoryStream = new MemoryStream(this.jpegBytes);
using var image = SDImage.FromStream(memoryStream);
return image.Size;
}
[Benchmark]
public Size ImageSharp()
{
using (var memoryStream = new MemoryStream(this.jpegBytes))
{
using (var image = Image.Load(memoryStream, new JpegDecoder { IgnoreMetadata = true }))
{
return new Size(image.Width, image.Height);
}
}
using var memoryStream = new MemoryStream(this.jpegBytes);
using var image = Image.Load(new DecoderOptions() { SkipMetadata = true }, memoryStream);
return new Size(image.Width, image.Height);
}
/*

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

@ -3,6 +3,7 @@
using System.IO;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Tests;
@ -31,8 +32,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
public IImageInfo Identify()
{
using var memoryStream = new MemoryStream(this.jpegBytes);
var decoder = new JpegDecoder();
return decoder.Identify(Configuration.Default, memoryStream, default);
IImageDecoder decoder = new JpegDecoder();
return decoder.Identify(DecoderOptions.Default, memoryStream, default);
}
}
}

5
tests/ImageSharp.Benchmarks/Codecs/Tga/DecodeTga.cs

@ -7,7 +7,6 @@ using System.Threading;
using BenchmarkDotNet.Attributes;
using ImageMagick;
using Pfim;
using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests;
@ -18,7 +17,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
{
private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage);
private readonly PfimConfig pfimConfig = new PfimConfig(allocator: new PfimAllocator());
private readonly PfimConfig pfimConfig = new(allocator: new PfimAllocator());
private byte[] data;
@ -40,7 +39,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
[Benchmark(Description = "ImageSharp Tga")]
public int TgaImageSharp()
{
using var image = Image.Load<Bgr24>(this.data, new TgaDecoder());
using var image = Image.Load<Bgr24>(this.data);
return image.Width;
}

4
tests/ImageSharp.Benchmarks/Codecs/Webp/DecodeWebp.cs

@ -55,7 +55,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
public int WebpLossy()
{
using var memoryStream = new MemoryStream(this.webpLossyBytes);
using var image = Image.Load<Rgba32>(this.configuration, memoryStream);
using var image = Image.Load<Rgba32>(memoryStream);
return image.Height;
}
@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
public int WebpLossless()
{
using var memoryStream = new MemoryStream(this.webpLosslessBytes);
using var image = Image.Load<Rgba32>(this.configuration, memoryStream);
using var image = Image.Load<Rgba32>(memoryStream);
return image.Height;
}

6
tests/ImageSharp.Benchmarks/General/Buffer2D_DangerousGetRowSpan.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -12,7 +11,8 @@ namespace SixLabors.ImageSharp.Benchmarks.General
{
private const int Height = 1024;
[Params(0.5, 2.0, 10.0)] public double SizeMegaBytes { get; set; }
[Params(0.5, 2.0, 10.0)]
public double SizeMegaBytes { get; set; }
private Buffer2D<Rgba32> buffer;

35
tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs

@ -14,6 +14,7 @@ using System.Threading;
using System.Threading.Tasks;
using ImageMagick;
using PhotoSauce.MagicScaler;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
@ -159,7 +160,7 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave
this.TotalProcessedMegapixels += pixels / 1_000_000.0;
}
private string OutputPath(string inputPath, [CallerMemberName]string postfix = null) =>
private string OutputPath(string inputPath, [CallerMemberName] string postfix = null) =>
Path.Combine(
this.outputDirectory,
Path.GetFileNameWithoutExtension(inputPath) + "-" + postfix + Path.GetExtension(inputPath));
@ -210,18 +211,15 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave
using FileStream outputStream = File.Open(this.OutputPath(input), FileMode.Create);
// Resize it to fit a 150x150 square
var targetSize = new ImageSharpSize(this.ThumbnailSize, this.ThumbnailSize);
DecoderOptions options = new()
{
TargetSize = new ImageSharpSize(this.ThumbnailSize, this.ThumbnailSize)
};
var decoder = new JpegDecoder();
using ImageSharpImage image = decoder.DecodeInto<Rgb24>(Configuration.Default, inputStream, targetSize, CancellationToken.None);
using ImageSharpImage image = decoder.Decode(options, inputStream);
this.LogImageProcessed(image.Width, image.Height);
image.Mutate(i => i.Resize(new ResizeOptions
{
Size = targetSize,
Mode = ResizeMode.Max,
Sampler = KnownResamplers.Box
}));
// Reduce the size of the file
image.Metadata.ExifProfile = null;
image.Metadata.XmpProfile = null;
@ -237,18 +235,19 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave
using FileStream output = File.Open(this.OutputPath(input), FileMode.Create);
// Resize it to fit a 150x150 square.
using ImageSharpImage image = await ImageSharpImage.LoadAsync(input);
this.LogImageProcessed(image.Width, image.Height);
// Resize checks whether image size and target sizes are equal
image.Mutate(i => i.Resize(new ResizeOptions
DecoderOptions options = new()
{
Size = new ImageSharpSize(this.ThumbnailSize, this.ThumbnailSize),
Mode = ResizeMode.Max
}));
TargetSize = new ImageSharpSize(this.ThumbnailSize, this.ThumbnailSize)
};
using ImageSharpImage image = await ImageSharpImage.LoadAsync(options, input);
this.LogImageProcessed(image.Width, image.Height);
// Reduce the size of the file
image.Metadata.ExifProfile = null;
image.Metadata.XmpProfile = null;
image.Metadata.IccProfile = null;
image.Metadata.IptcProfile = null;
// Save the results
await image.SaveAsync(output, this.imageSharpJpegEncoder);

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

@ -4,7 +4,7 @@
using System;
using System.IO;
using Microsoft.DotNet.RemoteExecutor;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
@ -75,11 +75,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
}
string providerDump = BasicSerializer.Serialize(provider);
RemoteExecutor.Invoke(
RunTest,
providerDump,
"Disco")
.Dispose();
RemoteExecutor.Invoke(RunTest, providerDump, "Disco").Dispose();
}
[Theory]
@ -98,11 +94,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecodeBitfields<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
[Theory]
@ -111,11 +105,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_Inverted<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
[Theory]
@ -124,11 +116,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_1Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, new SystemDrawingReferenceDecoder());
}
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider, new SystemDrawingReferenceDecoder());
}
[Theory]
@ -136,11 +126,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_4Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
[Theory]
@ -148,11 +136,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_8Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
[Theory]
@ -160,11 +146,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_16Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
[Theory]
@ -172,11 +156,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_32Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
[Theory]
@ -184,11 +166,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_32BitV4Header_Fast<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
[Theory]
@ -199,11 +179,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
where TPixel : unmanaged, IPixel<TPixel>
{
RleSkippedPixelHandling skippedPixelHandling = TestEnvironment.IsWindows ? RleSkippedPixelHandling.Black : RleSkippedPixelHandling.FirstColorOfPalette;
using (Image<TPixel> image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = skippedPixelHandling }))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
BmpDecoderOptions options = new() { RleSkippedPixelHandling = skippedPixelHandling };
using Image<TPixel> image = provider.GetImage(BmpDecoder, options);
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
[Theory]
@ -212,11 +192,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
where TPixel : unmanaged, IPixel<TPixel>
{
RleSkippedPixelHandling skippedPixelHandling = TestEnvironment.IsWindows ? RleSkippedPixelHandling.Black : RleSkippedPixelHandling.FirstColorOfPalette;
using (Image<TPixel> image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = skippedPixelHandling }))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
BmpDecoderOptions options = new() { RleSkippedPixelHandling = skippedPixelHandling };
using Image<TPixel> image = provider.GetImage(BmpDecoder, options);
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
[Theory]
@ -227,13 +207,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit_WithDelta_SystemDrawingRefDecoder<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = RleSkippedPixelHandling.Black }))
BmpDecoderOptions options = new() { RleSkippedPixelHandling = RleSkippedPixelHandling.Black };
using Image<TPixel> image = provider.GetImage(BmpDecoder, options);
image.DebugSave(provider);
if (TestEnvironment.IsWindows)
{
image.DebugSave(provider);
if (TestEnvironment.IsWindows)
{
image.CompareToOriginal(provider, new SystemDrawingReferenceDecoder());
}
image.CompareToOriginal(provider, new SystemDrawingReferenceDecoder());
}
}
@ -243,11 +222,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit_WithDelta_MagickRefDecoder<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = RleSkippedPixelHandling.FirstColorOfPalette }))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, new MagickReferenceDecoder());
}
BmpDecoderOptions options = new() { RleSkippedPixelHandling = RleSkippedPixelHandling.FirstColorOfPalette };
using Image<TPixel> image = provider.GetImage(BmpDecoder, options);
image.DebugSave(provider);
image.CompareToOriginal(provider, new MagickReferenceDecoder());
}
[Theory]
@ -263,11 +241,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
provider.LimitAllocatorBufferCapacity().InBytesSqrt(400);
}
using (Image<TPixel> image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = RleSkippedPixelHandling.FirstColorOfPalette }))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, new MagickReferenceDecoder());
}
BmpDecoderOptions options = new() { RleSkippedPixelHandling = RleSkippedPixelHandling.FirstColorOfPalette };
using Image<TPixel> image = provider.GetImage(BmpDecoder, options);
image.DebugSave(provider);
image.CompareToOriginal(provider, new MagickReferenceDecoder());
}
[Theory]
@ -285,13 +262,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
provider.LimitAllocatorBufferCapacity().InBytesSqrt(400);
}
using (Image<TPixel> image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = RleSkippedPixelHandling.Black }))
{
image.DebugSave(provider);
BmpDecoderOptions options = new() { RleSkippedPixelHandling = RleSkippedPixelHandling.Black };
using Image<TPixel> image = provider.GetImage(BmpDecoder, options);
image.DebugSave(provider);
// TODO: Neither System.Drawing nor MagickReferenceDecoder decode this file.
// image.CompareToOriginal(provider);
}
// TODO: Neither System.Drawing nor MagickReferenceDecoder decode this file.
// image.CompareToOriginal(provider);
}
[Theory]
@ -299,13 +275,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecodeAlphaBitfields<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
// TODO: Neither System.Drawing nor MagickReferenceDecoder decode this file.
// image.CompareToOriginal(provider);
}
// TODO: Neither System.Drawing nor MagickReferenceDecoder decode this file.
// image.CompareToOriginal(provider);
}
[Theory]
@ -313,11 +287,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecodeBitmap_WithAlphaChannel<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, new MagickReferenceDecoder());
}
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider, new MagickReferenceDecoder());
}
[Theory]
@ -325,17 +297,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecodeBitfields_WithUnusualBitmasks<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
// Choosing large tolerance of 6.1 here, because for some reason with the MagickReferenceDecoder the alpha channel
// seems to be wrong. This bitmap has an alpha channel of two bits. In many cases this alpha channel has a value of 3,
// which should be remapped to 255 for RGBA32, but the magick decoder has a value of 191 set.
// The total difference without the alpha channel is still: 0.0204%
// Exporting the image as PNG with GIMP yields to the same result as the ImageSharp implementation.
image.CompareToOriginal(provider, ImageComparer.TolerantPercentage(6.1f), new MagickReferenceDecoder());
}
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
// Choosing large tolerance of 6.1 here, because for some reason with the MagickReferenceDecoder the alpha channel
// seems to be wrong. This bitmap has an alpha channel of two bits. In many cases this alpha channel has a value of 3,
// which should be remapped to 255 for RGBA32, but the magick decoder has a value of 191 set.
// The total difference without the alpha channel is still: 0.0204%
// Exporting the image as PNG with GIMP yields to the same result as the ImageSharp implementation.
image.CompareToOriginal(provider, ImageComparer.TolerantPercentage(6.1f), new MagickReferenceDecoder());
}
[Theory]
@ -344,13 +314,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecodeBmpv2<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
// Do not validate. Reference files will fail validation.
image.CompareToOriginal(provider, new MagickReferenceDecoder(false));
}
// Do not validate. Reference files will fail validation.
image.CompareToOriginal(provider, new MagickReferenceDecoder(false));
}
[Theory]
@ -358,11 +326,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecodeBmpv3<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
[Theory]
@ -370,11 +336,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecodeLessThanFullPalette<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, new MagickReferenceDecoder());
}
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider, new MagickReferenceDecoder());
}
[Theory]
@ -383,13 +347,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecodeOversizedPalette<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
if (TestEnvironment.IsWindows)
{
image.DebugSave(provider);
if (TestEnvironment.IsWindows)
{
image.CompareToOriginal(provider);
}
image.CompareToOriginal(provider);
}
}
@ -397,39 +359,33 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
[WithFile(InvalidPaletteSize, PixelTypes.Rgba32)]
public void BmpDecoder_ThrowsInvalidImageContentException_OnInvalidPaletteSize<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
Assert.Throws<InvalidImageContentException>(() =>
=> Assert.Throws<InvalidImageContentException>(() =>
{
using (provider.GetImage(BmpDecoder))
{
}
});
}
[Theory]
[WithFile(Rgb24jpeg, PixelTypes.Rgba32)]
[WithFile(Rgb24png, PixelTypes.Rgba32)]
public void BmpDecoder_ThrowsNotSupportedException_OnUnsupportedBitmaps<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
Assert.Throws<NotSupportedException>(() =>
=> Assert.Throws<NotSupportedException>(() =>
{
using (provider.GetImage(BmpDecoder))
{
}
});
}
[Theory]
[WithFile(Rgb32h52AdobeV3, PixelTypes.Rgba32)]
public void BmpDecoder_CanDecodeAdobeBmpv3<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, new MagickReferenceDecoder());
}
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider, new MagickReferenceDecoder());
}
[Theory]
@ -437,11 +393,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecodeAdobeBmpv3_WithAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, new MagickReferenceDecoder());
}
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider, new MagickReferenceDecoder());
}
[Theory]
@ -449,11 +403,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecodeBmpv4<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
[Theory]
@ -462,11 +414,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecodeBmpv5<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
[Theory]
@ -474,11 +424,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_RespectsFileHeaderOffset<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
[Theory]
@ -486,11 +434,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
[Theory]
@ -498,11 +444,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode4BytePerEntryPalette<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
[Theory]
@ -521,12 +465,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void Identify_DetectsCorrectPixelType(string imagePath, int expectedPixelSize)
{
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
IImageInfo imageInfo = Image.Identify(stream);
Assert.NotNull(imageInfo);
Assert.Equal(expectedPixelSize, imageInfo.PixelType?.BitsPerPixel);
}
using var stream = new MemoryStream(testFile.Bytes, false);
IImageInfo imageInfo = Image.Identify(stream);
Assert.NotNull(imageInfo);
Assert.Equal(expectedPixelSize, imageInfo.PixelType?.BitsPerPixel);
}
[Theory]
@ -541,13 +483,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void Identify_DetectsCorrectWidthAndHeight(string imagePath, int expectedWidth, int expectedHeight)
{
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
IImageInfo imageInfo = Image.Identify(stream);
Assert.NotNull(imageInfo);
Assert.Equal(expectedWidth, imageInfo.Width);
Assert.Equal(expectedHeight, imageInfo.Height);
}
using var stream = new MemoryStream(testFile.Bytes, false);
IImageInfo imageInfo = Image.Identify(stream);
Assert.NotNull(imageInfo);
Assert.Equal(expectedWidth, imageInfo.Width);
Assert.Equal(expectedHeight, imageInfo.Height);
}
[Theory]
@ -555,17 +495,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
var decoder = new BmpDecoder();
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, stream, default))
{
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
}
using var stream = new MemoryStream(testFile.Bytes, false);
var decoder = new BmpDecoder();
using Image<Rgba32> image = decoder.Decode<Rgba32>(DecoderOptions.Default, stream);
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
[Theory]
@ -573,13 +509,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_Os2v2XShortHeader<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
// TODO: Neither System.Drawing or MagickReferenceDecoder can correctly decode this file.
// image.CompareToOriginal(provider);
}
// TODO: Neither System.Drawing or MagickReferenceDecoder can correctly decode this file.
// image.CompareToOriginal(provider);
}
[Theory]
@ -587,15 +521,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_Os2v2Header<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
// TODO: System.Drawing can not decode this image. MagickReferenceDecoder can decode it,
// but i think incorrectly. I have loaded the image with GIMP and exported as PNG.
// The results are the same as the image sharp implementation.
// image.CompareToOriginal(provider, new MagickReferenceDecoder());
}
// TODO: System.Drawing can not decode this image. MagickReferenceDecoder can decode it,
// but i think incorrectly. I have loaded the image with GIMP and exported as PNG.
// The results are the same as the image sharp implementation.
// image.CompareToOriginal(provider, new MagickReferenceDecoder());
}
[Theory]
@ -611,13 +543,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_Os2BitmapArray<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
// TODO: Neither System.Drawing or MagickReferenceDecoder can correctly decode this file.
// image.CompareToOriginal(provider);
}
// TODO: Neither System.Drawing or MagickReferenceDecoder can correctly decode this file.
// image.CompareToOriginal(provider);
}
}
}

194
tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs

@ -53,22 +53,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
var options = new BmpEncoder();
var testFile = TestFile.Create(imagePath);
using (Image<Rgba32> input = testFile.CreateRgba32Image())
{
using (var memStream = new MemoryStream())
{
input.Save(memStream, options);
memStream.Position = 0;
using (var output = Image.Load<Rgba32>(memStream))
{
ImageMetadata meta = output.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
}
}
using Image<Rgba32> input = testFile.CreateRgba32Image();
using var memStream = new MemoryStream();
input.Save(memStream, options);
memStream.Position = 0;
using var output = Image.Load<Rgba32>(memStream);
ImageMetadata meta = output.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
[Theory]
@ -78,21 +72,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
var options = new BmpEncoder();
var testFile = TestFile.Create(imagePath);
using (Image<Rgba32> input = testFile.CreateRgba32Image())
{
using (var memStream = new MemoryStream())
{
input.Save(memStream, options);
memStream.Position = 0;
using (var output = Image.Load<Rgba32>(memStream))
{
BmpMetadata meta = output.Metadata.GetBmpMetadata();
Assert.Equal(bmpBitsPerPixel, meta.BitsPerPixel);
}
}
}
using Image<Rgba32> input = testFile.CreateRgba32Image();
using var memStream = new MemoryStream();
input.Save(memStream, options);
memStream.Position = 0;
using var output = Image.Load<Rgba32>(memStream);
BmpMetadata meta = output.Metadata.GetBmpMetadata();
Assert.Equal(bmpBitsPerPixel, meta.BitsPerPixel);
}
[Theory]
@ -237,28 +225,26 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
return;
}
using (Image<TPixel> image = provider.GetImage())
using Image<TPixel> image = provider.GetImage();
var encoder = new BmpEncoder
{
var encoder = new BmpEncoder
{
BitsPerPixel = BmpBitsPerPixel.Pixel8,
Quantizer = new WuQuantizer()
};
string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "bmp", encoder, appendPixelTypeToFileName: false);
// Use the default decoder to test our encoded image. This verifies the content.
// We do not verify the reference image though as some are invalid.
IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile);
using (var referenceImage = Image.Load<TPixel>(actualOutputFile, referenceDecoder))
{
referenceImage.CompareToReferenceOutput(
ImageComparer.TolerantPercentage(0.01f),
provider,
extension: "bmp",
appendPixelTypeToFileName: false,
decoder: new MagickReferenceDecoder(false));
}
}
BitsPerPixel = BmpBitsPerPixel.Pixel8,
Quantizer = new WuQuantizer()
};
string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "bmp", encoder, appendPixelTypeToFileName: false);
// Use the default decoder to test our encoded image. This verifies the content.
// We do not verify the reference image though as some are invalid.
IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile);
using FileStream stream = File.OpenRead(actualOutputFile);
using Image<TPixel> referenceImage = referenceDecoder.Decode<TPixel>(DecoderOptions.Default, stream, default);
referenceImage.CompareToReferenceOutput(
ImageComparer.TolerantPercentage(0.01f),
provider,
extension: "bmp",
appendPixelTypeToFileName: false,
decoder: new MagickReferenceDecoder(false));
}
[Theory]
@ -271,28 +257,25 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
return;
}
using (Image<TPixel> image = provider.GetImage())
using Image<TPixel> image = provider.GetImage();
var encoder = new BmpEncoder
{
var encoder = new BmpEncoder
{
BitsPerPixel = BmpBitsPerPixel.Pixel8,
Quantizer = new OctreeQuantizer()
};
string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "bmp", encoder, appendPixelTypeToFileName: false);
// Use the default decoder to test our encoded image. This verifies the content.
// We do not verify the reference image though as some are invalid.
IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile);
using (var referenceImage = Image.Load<TPixel>(actualOutputFile, referenceDecoder))
{
referenceImage.CompareToReferenceOutput(
ImageComparer.TolerantPercentage(0.01f),
provider,
extension: "bmp",
appendPixelTypeToFileName: false,
decoder: new MagickReferenceDecoder(false));
}
}
BitsPerPixel = BmpBitsPerPixel.Pixel8,
Quantizer = new OctreeQuantizer()
};
string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "bmp", encoder, appendPixelTypeToFileName: false);
// Use the default decoder to test our encoded image. This verifies the content.
// We do not verify the reference image though as some are invalid.
IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile);
using FileStream stream = File.OpenRead(actualOutputFile);
using Image<TPixel> referenceImage = referenceDecoder.Decode<TPixel>(DecoderOptions.Default, stream, default);
referenceImage.CompareToReferenceOutput(
ImageComparer.TolerantPercentage(0.01f),
provider,
extension: "bmp",
appendPixelTypeToFileName: false,
decoder: new MagickReferenceDecoder(false));
}
[Theory]
@ -306,26 +289,20 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void Encode_PreservesColorProfile<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> input = provider.GetImage(new BmpDecoder()))
{
ImageSharp.Metadata.Profiles.Icc.IccProfile expectedProfile = input.Metadata.IccProfile;
byte[] expectedProfileBytes = expectedProfile.ToByteArray();
using (var memStream = new MemoryStream())
{
input.Save(memStream, new BmpEncoder());
memStream.Position = 0;
using (var output = Image.Load<Rgba32>(memStream))
{
ImageSharp.Metadata.Profiles.Icc.IccProfile actualProfile = output.Metadata.IccProfile;
byte[] actualProfileBytes = actualProfile.ToByteArray();
Assert.NotNull(actualProfile);
Assert.Equal(expectedProfileBytes, actualProfileBytes);
}
}
}
using Image<TPixel> input = provider.GetImage(new BmpDecoder(), new());
ImageSharp.Metadata.Profiles.Icc.IccProfile expectedProfile = input.Metadata.IccProfile;
byte[] expectedProfileBytes = expectedProfile.ToByteArray();
using var memStream = new MemoryStream();
input.Save(memStream, new BmpEncoder());
memStream.Position = 0;
using var output = Image.Load<Rgba32>(memStream);
ImageSharp.Metadata.Profiles.Icc.IccProfile actualProfile = output.Metadata.IccProfile;
byte[] actualProfileBytes = actualProfile.ToByteArray();
Assert.NotNull(actualProfile);
Assert.Equal(expectedProfileBytes, actualProfileBytes);
}
[Theory]
@ -346,24 +323,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
ImageComparer customComparer = null)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
using Image<TPixel> image = provider.GetImage();
// There is no alpha in bmp with less then 32 bits per pixels, so the reference image will be made opaque.
if (bitsPerPixel != BmpBitsPerPixel.Pixel32)
{
// There is no alpha in bmp with less then 32 bits per pixels, so the reference image will be made opaque.
if (bitsPerPixel != BmpBitsPerPixel.Pixel32)
{
image.Mutate(c => c.MakeOpaque());
}
var encoder = new BmpEncoder
{
BitsPerPixel = bitsPerPixel,
SupportTransparency = supportTransparency,
Quantizer = quantizer ?? KnownQuantizers.Octree
};
// Does DebugSave & load reference CompareToReferenceInput():
image.VerifyEncoder(provider, "bmp", bitsPerPixel, encoder, customComparer);
image.Mutate(c => c.MakeOpaque());
}
var encoder = new BmpEncoder
{
BitsPerPixel = bitsPerPixel,
SupportTransparency = supportTransparency,
Quantizer = quantizer ?? KnownQuantizers.Octree
};
// Does DebugSave & load reference CompareToReferenceInput():
image.VerifyEncoder(provider, "bmp", bitsPerPixel, encoder, customComparer);
}
}
}

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

@ -45,12 +45,10 @@ namespace SixLabors.ImageSharp.Tests.Formats
public void ResolutionShouldChange<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
image.Metadata.VerticalResolution = 150;
image.Metadata.HorizontalResolution = 150;
image.DebugSave(provider);
}
using Image<TPixel> image = provider.GetImage();
image.Metadata.VerticalResolution = 150;
image.Metadata.HorizontalResolution = 150;
image.DebugSave(provider);
}
[Fact]
@ -60,11 +58,9 @@ namespace SixLabors.ImageSharp.Tests.Formats
foreach (TestFile file in Files)
{
using (Image<Rgba32> image = file.CreateRgba32Image())
{
string filename = Path.Combine(path, $"{file.FileNameWithoutExtension}.txt");
File.WriteAllText(filename, image.ToBase64String(PngFormat.Instance));
}
using Image<Rgba32> image = file.CreateRgba32Image();
string filename = Path.Combine(path, $"{file.FileNameWithoutExtension}.txt");
File.WriteAllText(filename, image.ToBase64String(PngFormat.Instance));
}
}
@ -75,10 +71,8 @@ namespace SixLabors.ImageSharp.Tests.Formats
foreach (TestFile file in Files)
{
using (Image<Rgba32> image = file.CreateRgba32Image())
{
image.Save(Path.Combine(path, file.FileName));
}
using Image<Rgba32> image = file.CreateRgba32Image();
image.Save(Path.Combine(path, file.FileName));
}
}
@ -120,42 +114,40 @@ namespace SixLabors.ImageSharp.Tests.Formats
foreach (TestFile file in Files)
{
using (Image<Rgba32> image = file.CreateRgba32Image())
using Image<Rgba32> image = file.CreateRgba32Image();
using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.bmp")))
{
image.SaveAsBmp(output);
}
using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.jpg")))
{
image.SaveAsJpeg(output);
}
using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.pbm")))
{
image.SaveAsPbm(output);
}
using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.png")))
{
image.SaveAsPng(output);
}
using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.gif")))
{
image.SaveAsGif(output);
}
using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.tga")))
{
using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.bmp")))
{
image.SaveAsBmp(output);
}
using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.jpg")))
{
image.SaveAsJpeg(output);
}
using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.pbm")))
{
image.SaveAsPbm(output);
}
using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.png")))
{
image.SaveAsPng(output);
}
using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.gif")))
{
image.SaveAsGif(output);
}
using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.tga")))
{
image.SaveAsTga(output);
}
using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.tiff")))
{
image.SaveAsTiff(output);
}
image.SaveAsTga(output);
}
using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.tiff")))
{
image.SaveAsTiff(output);
}
}
}
@ -176,10 +168,8 @@ namespace SixLabors.ImageSharp.Tests.Formats
serialized = memoryStream.ToArray();
}
using (var image2 = Image.Load<Rgba32>(serialized))
{
image2.Save($"{path}{Path.DirectorySeparatorChar}{file.FileName}");
}
using var image2 = Image.Load<Rgba32>(serialized);
image2.Save($"{path}{Path.DirectorySeparatorChar}{file.FileName}");
}
}
@ -213,39 +203,33 @@ namespace SixLabors.ImageSharp.Tests.Formats
public void CanIdentifyImageLoadedFromBytes(int width, int height, string extension)
{
using (var image = Image.LoadPixelData(new Rgba32[width * height], width, height))
{
using (var memoryStream = new MemoryStream())
{
IImageFormat format = GetFormat(extension);
image.Save(memoryStream, format);
memoryStream.Position = 0;
using var image = Image.LoadPixelData<Rgba32>(new Rgba32[width * height], width, height);
using var memoryStream = new MemoryStream();
IImageFormat format = GetFormat(extension);
image.Save(memoryStream, format);
memoryStream.Position = 0;
IImageInfo imageInfo = Image.Identify(memoryStream);
IImageInfo imageInfo = Image.Identify(memoryStream);
Assert.Equal(imageInfo.Width, width);
Assert.Equal(imageInfo.Height, height);
memoryStream.Position = 0;
Assert.Equal(imageInfo.Width, width);
Assert.Equal(imageInfo.Height, height);
memoryStream.Position = 0;
imageInfo = Image.Identify(memoryStream, out IImageFormat detectedFormat);
imageInfo = Image.Identify(memoryStream, out IImageFormat detectedFormat);
Assert.Equal(format, detectedFormat);
}
}
Assert.Equal(format, detectedFormat);
}
[Fact]
public void IdentifyReturnsNullWithInvalidStream()
{
var invalid = new byte[10];
byte[] invalid = new byte[10];
using (var memoryStream = new MemoryStream(invalid))
{
IImageInfo imageInfo = Image.Identify(memoryStream, out IImageFormat format);
using var memoryStream = new MemoryStream(invalid);
IImageInfo imageInfo = Image.Identify(memoryStream, out IImageFormat format);
Assert.Null(imageInfo);
Assert.Null(format);
}
Assert.Null(imageInfo);
Assert.Null(format);
}
private static IImageFormat GetFormat(string format)

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

@ -4,10 +4,9 @@
using System;
using System.IO;
using Microsoft.DotNet.RemoteExecutor;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
@ -35,11 +34,32 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
public void Decode_VerifyAllFrames<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
using Image<TPixel> image = provider.GetImage();
image.DebugSaveMultiFrame(provider);
image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact);
}
[Theory]
[WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)]
public void GifDecoder_Decode_Resize<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
DecoderOptions options = new()
{
image.DebugSaveMultiFrame(provider);
image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact);
}
TargetSize = new() { Width = 150, Height = 150 },
MaxFrames = 1
};
using Image<TPixel> image = provider.GetImage(GifDecoder, options);
FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}";
image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false);
image.CompareToReferenceOutput(
ImageComparer.TolerantPercentage(0.0001F),
provider,
testOutputDetails: details,
appendPixelTypeToFileName: false);
}
[Fact]
@ -51,13 +71,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
fixed (byte* data = testFile.Bytes.AsSpan(0, length))
{
using (var stream = new UnmanagedMemoryStream(data, length))
{
using (Image<Rgba32> image = GifDecoder.Decode<Rgba32>(Configuration.Default, stream, default))
{
Assert.Equal((200, 200), (image.Width, image.Height));
}
}
using var stream = new UnmanagedMemoryStream(data, length);
using Image<Rgba32> image = GifDecoder.Decode<Rgba32>(DecoderOptions.Default, stream);
Assert.Equal((200, 200), (image.Width, image.Height));
}
}
@ -66,11 +82,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
public void GifDecoder_IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
image.DebugSave(provider);
image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider);
}
using Image<TPixel> image = provider.GetImage();
image.DebugSave(provider);
image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider);
}
[Theory]
@ -80,12 +94,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
public void Decode_VerifyRootFrameAndFrameCount<TPixel>(TestImageProvider<TPixel> provider, int expectedFrameCount)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
Assert.Equal(expectedFrameCount, image.Frames.Count);
image.DebugSave(provider);
image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider);
}
using Image<TPixel> image = provider.GetImage();
Assert.Equal(expectedFrameCount, image.Frames.Count);
image.DebugSave(provider);
image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider);
}
[Theory]
@ -93,10 +105,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
public void CanDecodeJustOneFrame<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new GifDecoder { DecodingMode = FrameDecodingMode.First }))
{
Assert.Equal(1, image.Frames.Count);
}
DecoderOptions options = new() { MaxFrames = 1 };
using Image<TPixel> image = provider.GetImage(new GifDecoder(), options);
Assert.Equal(1, image.Frames.Count);
}
[Theory]
@ -104,10 +115,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
public void CanDecodeAllFrames<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new GifDecoder { DecodingMode = FrameDecodingMode.All }))
{
Assert.True(image.Frames.Count > 1);
}
using Image<TPixel> image = provider.GetImage(new GifDecoder());
Assert.True(image.Frames.Count > 1);
}
[Theory]
@ -118,10 +127,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
public void DetectPixelSize(string imagePath, int expectedPixelSize)
{
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
Assert.Equal(expectedPixelSize, Image.Identify(stream)?.PixelType?.BitsPerPixel);
}
using var stream = new MemoryStream(testFile.Bytes, false);
Assert.Equal(expectedPixelSize, Image.Identify(stream)?.PixelType?.BitsPerPixel);
}
[Theory]
@ -146,11 +153,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
public void Decode_WithMaxDimensions_Works<TPixel>(TestImageProvider<TPixel> provider, int expectedWidth, int expectedHeight)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(GifDecoder))
{
Assert.Equal(expectedWidth, image.Width);
Assert.Equal(expectedHeight, image.Height);
}
using Image<TPixel> image = provider.GetImage(GifDecoder);
Assert.Equal(expectedWidth, image.Width);
Assert.Equal(expectedHeight, image.Height);
}
[Fact]
@ -190,12 +195,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
public void Issue405_BadApplicationExtensionBlockLength<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
image.DebugSave(provider);
using Image<TPixel> image = provider.GetImage();
image.DebugSave(provider);
image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider);
}
image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider);
}
// https://github.com/SixLabors/ImageSharp/issues/1668
@ -204,12 +207,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
public void Issue1668_InvalidColorIndex<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
image.DebugSave(provider);
using Image<TPixel> image = provider.GetImage();
image.DebugSave(provider);
image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider);
}
image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider);
}
[Theory]

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

@ -4,6 +4,8 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
@ -58,51 +60,39 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
[Fact]
public void Decode_IgnoreMetadataIsFalse_CommentsAreRead()
{
var options = new GifDecoder
{
IgnoreMetadata = false
};
var testFile = TestFile.Create(TestImages.Gif.Rings);
using (Image<Rgba32> image = testFile.CreateRgba32Image(options))
{
GifMetadata metadata = image.Metadata.GetGifMetadata();
Assert.Equal(1, metadata.Comments.Count);
Assert.Equal("ImageSharp", metadata.Comments[0]);
}
using Image<Rgba32> image = testFile.CreateRgba32Image(new GifDecoder());
GifMetadata metadata = image.Metadata.GetGifMetadata();
Assert.Equal(1, metadata.Comments.Count);
Assert.Equal("ImageSharp", metadata.Comments[0]);
}
[Fact]
public void Decode_IgnoreMetadataIsTrue_CommentsAreIgnored()
{
var options = new GifDecoder
DecoderOptions options = new()
{
IgnoreMetadata = true
SkipMetadata = true
};
var testFile = TestFile.Create(TestImages.Gif.Rings);
using (Image<Rgba32> image = testFile.CreateRgba32Image(options))
{
GifMetadata metadata = image.Metadata.GetGifMetadata();
Assert.Equal(0, metadata.Comments.Count);
}
using Image<Rgba32> image = testFile.CreateRgba32Image(new GifDecoder(), options);
GifMetadata metadata = image.Metadata.GetGifMetadata();
Assert.Equal(0, metadata.Comments.Count);
}
[Fact]
public void Decode_CanDecodeLargeTextComment()
{
var options = new GifDecoder();
var testFile = TestFile.Create(TestImages.Gif.LargeComment);
using (Image<Rgba32> image = testFile.CreateRgba32Image(options))
{
GifMetadata metadata = image.Metadata.GetGifMetadata();
Assert.Equal(2, metadata.Comments.Count);
Assert.Equal(new string('c', 349), metadata.Comments[0]);
Assert.Equal("ImageSharp", metadata.Comments[1]);
}
using Image<Rgba32> image = testFile.CreateRgba32Image(new GifDecoder());
GifMetadata metadata = image.Metadata.GetGifMetadata();
Assert.Equal(2, metadata.Comments.Count);
Assert.Equal(new string('c', 349), metadata.Comments[0]);
Assert.Equal("ImageSharp", metadata.Comments[1]);
}
[Fact]
@ -111,20 +101,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
var decoder = new GifDecoder();
var testFile = TestFile.Create(TestImages.Gif.LargeComment);
using (Image<Rgba32> input = testFile.CreateRgba32Image(decoder))
using (var memoryStream = new MemoryStream())
{
input.Save(memoryStream, new GifEncoder());
memoryStream.Position = 0;
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, memoryStream, default))
{
GifMetadata metadata = image.Metadata.GetGifMetadata();
Assert.Equal(2, metadata.Comments.Count);
Assert.Equal(new string('c', 349), metadata.Comments[0]);
Assert.Equal("ImageSharp", metadata.Comments[1]);
}
}
using Image<Rgba32> input = testFile.CreateRgba32Image(decoder);
using var memoryStream = new MemoryStream();
input.Save(memoryStream, new GifEncoder());
memoryStream.Position = 0;
using Image<Rgba32> image = decoder.Decode<Rgba32>(DecoderOptions.Default, memoryStream);
GifMetadata metadata = image.Metadata.GetGifMetadata();
Assert.Equal(2, metadata.Comments.Count);
Assert.Equal(new string('c', 349), metadata.Comments[0]);
Assert.Equal("ImageSharp", metadata.Comments[1]);
}
[Theory]
@ -132,15 +118,27 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
var decoder = new GifDecoder();
IImageInfo image = decoder.Identify(Configuration.Default, stream, default);
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
using var stream = new MemoryStream(testFile.Bytes, false);
var decoder = new GifDecoder();
IImageInfo image = decoder.Identify(DecoderOptions.Default, stream);
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
[Theory]
[MemberData(nameof(RatioFiles))]
public async Task Identify_VerifyRatioAsync(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
var decoder = new GifDecoder();
IImageInfo image = await decoder.IdentifyAsync(DecoderOptions.Default, stream);
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
[Theory]
@ -148,17 +146,27 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
var decoder = new GifDecoder();
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, stream, default))
{
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
}
using var stream = new MemoryStream(testFile.Bytes, false);
var decoder = new GifDecoder();
using Image<Rgba32> image = decoder.Decode<Rgba32>(DecoderOptions.Default, stream);
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
[Theory]
[MemberData(nameof(RatioFiles))]
public async Task Decode_VerifyRatioAsync(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
var decoder = new GifDecoder();
using Image<Rgba32> image = await decoder.DecodeAsync<Rgba32>(DecoderOptions.Default, stream);
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
[Theory]
@ -166,13 +174,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
public void Identify_VerifyRepeatCount(string imagePath, uint repeatCount)
{
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
var decoder = new GifDecoder();
IImageInfo image = decoder.Identify(Configuration.Default, stream, default);
GifMetadata meta = image.Metadata.GetGifMetadata();
Assert.Equal(repeatCount, meta.RepeatCount);
}
using var stream = new MemoryStream(testFile.Bytes, false);
var decoder = new GifDecoder();
IImageInfo image = decoder.Identify(DecoderOptions.Default, stream);
GifMetadata meta = image.Metadata.GetGifMetadata();
Assert.Equal(repeatCount, meta.RepeatCount);
}
[Theory]
@ -180,15 +186,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
public void Decode_VerifyRepeatCount(string imagePath, uint repeatCount)
{
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
var decoder = new GifDecoder();
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, stream, default))
{
GifMetadata meta = image.Metadata.GetGifMetadata();
Assert.Equal(repeatCount, meta.RepeatCount);
}
}
using var stream = new MemoryStream(testFile.Bytes, false);
var decoder = new GifDecoder();
using Image<Rgba32> image = decoder.Decode<Rgba32>(DecoderOptions.Default, stream);
GifMetadata meta = image.Metadata.GetGifMetadata();
Assert.Equal(repeatCount, meta.RepeatCount);
}
}
}

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

@ -4,6 +4,7 @@
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Metadata;
@ -80,17 +81,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
var decoder = new JpegDecoder();
using (Image image = decoder.Decode(Configuration.Default, stream, default))
{
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
}
using var stream = new MemoryStream(testFile.Bytes, false);
var decoder = new JpegDecoder();
using Image image = decoder.Decode(DecoderOptions.Default, stream);
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
[Theory]
@ -98,15 +95,27 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
var decoder = new JpegDecoder();
IImageInfo image = decoder.Identify(Configuration.Default, stream, default);
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
using var stream = new MemoryStream(testFile.Bytes, false);
var decoder = new JpegDecoder();
IImageInfo image = decoder.Identify(DecoderOptions.Default, stream);
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
[Theory]
[MemberData(nameof(RatioFiles))]
public async Task Identify_VerifyRatioAsync(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
var decoder = new JpegDecoder();
IImageInfo image = await decoder.IdentifyAsync(DecoderOptions.Default, stream);
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
[Theory]
@ -114,13 +123,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
public void Identify_VerifyQuality(string imagePath, int quality)
{
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
var decoder = new JpegDecoder();
IImageInfo image = decoder.Identify(Configuration.Default, stream, default);
JpegMetadata meta = image.Metadata.GetJpegMetadata();
Assert.Equal(quality, meta.Quality);
}
using var stream = new MemoryStream(testFile.Bytes, false);
var decoder = new JpegDecoder();
IImageInfo image = decoder.Identify(DecoderOptions.Default, stream);
JpegMetadata meta = image.Metadata.GetJpegMetadata();
Assert.Equal(quality, meta.Quality);
}
[Theory]
@ -128,14 +135,21 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
public void Decode_VerifyQuality(string imagePath, int quality)
{
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
using (Image image = JpegDecoder.Decode(Configuration.Default, stream, default))
{
JpegMetadata meta = image.Metadata.GetJpegMetadata();
Assert.Equal(quality, meta.Quality);
}
}
using var stream = new MemoryStream(testFile.Bytes, false);
using Image image = JpegDecoder.Decode(DecoderOptions.Default, stream);
JpegMetadata meta = image.Metadata.GetJpegMetadata();
Assert.Equal(quality, meta.Quality);
}
[Theory]
[MemberData(nameof(QualityFiles))]
public async Task Decode_VerifyQualityAsync(string imagePath, int quality)
{
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
using Image image = await JpegDecoder.DecodeAsync(DecoderOptions.Default, stream);
JpegMetadata meta = image.Metadata.GetJpegMetadata();
Assert.Equal(quality, meta.Quality);
}
[Theory]
@ -149,12 +163,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
public void Identify_DetectsCorrectColorType(string imagePath, JpegEncodingColor expectedColorType)
{
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
IImageInfo image = JpegDecoder.Identify(Configuration.Default, stream, default);
JpegMetadata meta = image.Metadata.GetJpegMetadata();
Assert.Equal(expectedColorType, meta.ColorType);
}
using var stream = new MemoryStream(testFile.Bytes, false);
IImageInfo image = JpegDecoder.Identify(DecoderOptions.Default, stream);
JpegMetadata meta = image.Metadata.GetJpegMetadata();
Assert.Equal(expectedColorType, meta.ColorType);
}
[Theory]
@ -166,28 +178,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
public void Decode_DetectsCorrectColorType<TPixel>(TestImageProvider<TPixel> provider, JpegEncodingColor expectedColorType)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(JpegDecoder))
{
JpegMetadata meta = image.Metadata.GetJpegMetadata();
Assert.Equal(expectedColorType, meta.ColorType);
}
using Image<TPixel> image = provider.GetImage(JpegDecoder);
JpegMetadata meta = image.Metadata.GetJpegMetadata();
Assert.Equal(expectedColorType, meta.ColorType);
}
private static void TestImageInfo(string imagePath, IImageDecoder decoder, bool useIdentify, Action<IImageInfo> test)
{
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
using var stream = new MemoryStream(testFile.Bytes, false);
if (useIdentify)
{
if (useIdentify)
{
IImageInfo imageInfo = ((IImageInfoDetector)decoder).Identify(Configuration.Default, stream, default);
test(imageInfo);
}
else
{
using var img = decoder.Decode<Rgba32>(Configuration.Default, stream, default);
test(img);
}
IImageInfo imageInfo = decoder.Identify(DecoderOptions.Default, stream, default);
test(imageInfo);
}
else
{
using Image<Rgba32> img = decoder.Decode<Rgba32>(DecoderOptions.Default, stream, default);
test(img);
}
}
@ -247,23 +255,21 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[InlineData(true)]
public void IgnoreMetadata_ControlsWhetherMetadataIsParsed(bool ignoreMetadata)
{
var decoder = new JpegDecoder { IgnoreMetadata = ignoreMetadata };
DecoderOptions options = new() { SkipMetadata = ignoreMetadata };
// Snake.jpg has both Exif and ICC profiles defined:
var testFile = TestFile.Create(TestImages.Jpeg.Baseline.Snake);
using (Image<Rgba32> image = testFile.CreateRgba32Image(decoder))
using Image<Rgba32> image = testFile.CreateRgba32Image(JpegDecoder, options);
if (ignoreMetadata)
{
if (ignoreMetadata)
{
Assert.Null(image.Metadata.ExifProfile);
Assert.Null(image.Metadata.IccProfile);
}
else
{
Assert.NotNull(image.Metadata.ExifProfile);
Assert.NotNull(image.Metadata.IccProfile);
}
Assert.Null(image.Metadata.ExifProfile);
Assert.Null(image.Metadata.IccProfile);
}
else
{
Assert.NotNull(image.Metadata.ExifProfile);
Assert.NotNull(image.Metadata.IccProfile);
}
}
@ -313,7 +319,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Exception ex = Record.Exception(() =>
{
using Image<TPixel> image = provider.GetImage(JpegDecoder);
var clone = image.Metadata.ExifProfile.DeepClone();
ExifProfile clone = image.Metadata.ExifProfile.DeepClone();
});
Assert.Null(ex);
}
@ -356,11 +362,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Fact]
public void EncodedStringTags_Read()
{
using (var image = Image.Load(TestFile.GetInputFileFullPath(TestImages.Jpeg.Baseline.Calliphora_EncodedStrings)))
{
ExifProfile exif = image.Metadata.ExifProfile;
VerifyEncodedStrings(exif);
}
using var image = Image.Load(TestFile.GetInputFileFullPath(TestImages.Jpeg.Baseline.Calliphora_EncodedStrings));
ExifProfile exif = image.Metadata.ExifProfile;
VerifyEncodedStrings(exif);
}
private static void VerifyEncodedStrings(ExifProfile exif)

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

@ -6,10 +6,12 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils;
using SixLabors.ImageSharp.Tests.TestUtilities;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
@ -73,10 +75,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Fact]
public void ParseStream_BasicPropertiesAreCorrect()
{
JpegDecoderOptions options = new();
byte[] bytes = TestFile.Create(TestImages.Jpeg.Progressive.Progress).Bytes;
using var ms = new MemoryStream(bytes);
using var bufferedStream = new BufferedReadStream(Configuration.Default, ms);
using var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder());
using var decoder = new JpegDecoderCore(options);
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
@ -118,6 +121,118 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
appendPixelTypeToFileName: false);
}
[Theory]
[WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24)]
public void JpegDecoder_Decode_Resize<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
DecoderOptions options = new() { TargetSize = new() { Width = 150, Height = 150 } };
using Image<TPixel> image = provider.GetImage(JpegDecoder, options);
FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}";
image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false);
image.CompareToReferenceOutput(
ImageComparer.Tolerant(BaselineTolerance),
provider,
testOutputDetails: details,
appendPixelTypeToFileName: false);
}
[Theory]
[WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24)]
public void JpegDecoder_Decode_Resize_Bicubic<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
DecoderOptions options = new()
{
TargetSize = new() { Width = 150, Height = 150 },
Sampler = KnownResamplers.Bicubic
};
using Image<TPixel> image = provider.GetImage(JpegDecoder, options);
FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}";
image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false);
image.CompareToReferenceOutput(
ImageComparer.Tolerant(BaselineTolerance),
provider,
testOutputDetails: details,
appendPixelTypeToFileName: false);
}
[Theory]
[WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24)]
public void JpegDecoder_Decode_Specialized_IDCT_Resize<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
DecoderOptions options = new() { TargetSize = new() { Width = 150, Height = 150 } };
JpegDecoderOptions specializedOptions = new()
{
GeneralOptions = options,
ResizeMode = JpegDecoderResizeMode.IdctOnly
};
using Image<TPixel> image = provider.GetImage(JpegDecoder, specializedOptions);
FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}";
image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false);
image.CompareToReferenceOutput(
ImageComparer.Tolerant(BaselineTolerance),
provider,
testOutputDetails: details,
appendPixelTypeToFileName: false);
}
[Theory]
[WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24)]
public void JpegDecoder_Decode_Specialized_Scale_Resize<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
DecoderOptions options = new() { TargetSize = new() { Width = 150, Height = 150 } };
JpegDecoderOptions specializedOptions = new()
{
GeneralOptions = options,
ResizeMode = JpegDecoderResizeMode.ScaleOnly
};
using Image<TPixel> image = provider.GetImage(JpegDecoder, specializedOptions);
FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}";
image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false);
image.CompareToReferenceOutput(
ImageComparer.Tolerant(BaselineTolerance),
provider,
testOutputDetails: details,
appendPixelTypeToFileName: false);
}
[Theory]
[WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24)]
public void JpegDecoder_Decode_Specialized_Combined_Resize<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
DecoderOptions options = new() { TargetSize = new() { Width = 150, Height = 150 } };
JpegDecoderOptions specializedOptions = new()
{
GeneralOptions = options,
ResizeMode = JpegDecoderResizeMode.Combined
};
using Image<TPixel> image = provider.GetImage(JpegDecoder, specializedOptions);
FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}";
image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false);
image.CompareToReferenceOutput(
ImageComparer.Tolerant(BaselineTolerance),
provider,
testOutputDetails: details,
appendPixelTypeToFileName: false);
}
[Theory]
[WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgba32)]
[WithFile(TestImages.Jpeg.Progressive.Festzug, PixelTypes.Rgba32)]
@ -148,17 +263,22 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
var cts = new CancellationTokenSource();
string file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.Jpeg420Small);
using var pausedStream = new PausedStream(file);
pausedStream.OnWaiting(s =>
pausedStream.OnWaiting(_ =>
{
cts.Cancel();
pausedStream.Release();
});
var config = Configuration.CreateDefaultInstance();
config.FileSystem = new SingleStreamFileSystem(pausedStream);
var configuration = Configuration.CreateDefaultInstance();
configuration.FileSystem = new SingleStreamFileSystem(pausedStream);
DecoderOptions options = new()
{
Configuration = configuration
};
await Assert.ThrowsAsync<TaskCanceledException>(async () =>
{
using Image image = await Image.LoadAsync(config, "someFakeFile", cts.Token);
using Image image = await Image.LoadAsync(options, "someFakeFile", cts.Token);
});
}
@ -169,28 +289,30 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
string file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.Jpeg420Small);
using var pausedStream = new PausedStream(file);
pausedStream.OnWaiting(s =>
pausedStream.OnWaiting(_ =>
{
cts.Cancel();
pausedStream.Release();
});
var config = Configuration.CreateDefaultInstance();
config.FileSystem = new SingleStreamFileSystem(pausedStream);
var configuration = Configuration.CreateDefaultInstance();
configuration.FileSystem = new SingleStreamFileSystem(pausedStream);
DecoderOptions options = new()
{
Configuration = configuration
};
await Assert.ThrowsAsync<TaskCanceledException>(async () => await Image.IdentifyAsync(config, "someFakeFile", cts.Token));
await Assert.ThrowsAsync<TaskCanceledException>(async () => await Image.IdentifyAsync(options, "someFakeFile", cts.Token));
}
[Theory]
[WithFileCollection(nameof(UnsupportedTestJpegs), PixelTypes.Rgba32)]
public void ThrowsNotSupported_WithUnsupportedJpegs<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
Assert.Throws<NotSupportedException>(() =>
=> Assert.Throws<NotSupportedException>(() =>
{
using Image<TPixel> image = provider.GetImage(JpegDecoder);
});
}
// https://github.com/SixLabors/ImageSharp/pull/1732
[Theory]
@ -198,11 +320,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
public void Issue1732_DecodesWithRgbColorSpace<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(JpegDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
using Image<TPixel> image = provider.GetImage(JpegDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
// https://github.com/SixLabors/ImageSharp/issues/2057
@ -211,11 +331,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
public void Issue2057_DecodeWorks<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(JpegDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
using Image<TPixel> image = provider.GetImage(JpegDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
// https://github.com/SixLabors/ImageSharp/issues/2133
@ -224,11 +342,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
public void Issue2133_DeduceColorSpace<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(JpegDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
using Image<TPixel> image = provider.GetImage(JpegDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
// https://github.com/SixLabors/ImageSharp/issues/2133
@ -237,44 +353,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
public void Issue2136_DecodeWorks<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(JpegDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
}
// DEBUG ONLY!
// The PDF.js output should be saved by "tests\ImageSharp.Tests\Formats\Jpg\pdfjs\jpeg-converter.htm"
// into "\tests\Images\ActualOutput\JpegDecoderTests\"
// [Theory]
// [WithFile(TestImages.Jpeg.Progressive.Progress, PixelTypes.Rgba32, "PdfJsOriginal_progress.png")]
public void ValidateProgressivePdfJsOutput<TPixel>(
TestImageProvider<TPixel> provider,
string pdfJsOriginalResultImage)
where TPixel : unmanaged, IPixel<TPixel>
{
// tests\ImageSharp.Tests\Formats\Jpg\pdfjs\jpeg-converter.htm
string pdfJsOriginalResultPath = Path.Combine(
provider.Utility.GetTestOutputDir(),
pdfJsOriginalResultImage);
byte[] sourceBytes = TestFile.Create(TestImages.Jpeg.Progressive.Progress).Bytes;
provider.Utility.TestName = nameof(DecodeProgressiveJpegOutputName);
var comparer = ImageComparer.Tolerant(0, 0);
using (Image<TPixel> expectedImage = provider.GetReferenceOutputImage<TPixel>(appendPixelTypeToFileName: false))
using (var pdfJsOriginalResult = Image.Load<Rgba32>(pdfJsOriginalResultPath))
using (var pdfJsPortResult = Image.Load<Rgba32>(sourceBytes, JpegDecoder))
{
ImageSimilarityReport originalReport = comparer.CompareImagesOrFrames(expectedImage, pdfJsOriginalResult);
ImageSimilarityReport portReport = comparer.CompareImagesOrFrames(expectedImage, pdfJsPortResult);
this.Output.WriteLine($"Difference for PDF.js ORIGINAL: {originalReport.DifferencePercentageString}");
this.Output.WriteLine($"Difference for PORT: {portReport.DifferencePercentageString}");
}
using Image<TPixel> image = provider.GetImage(JpegDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
}
}

6
tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs

@ -49,8 +49,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
// Calculating data from ImageSharp
byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes;
JpegDecoderOptions option = new();
using var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder());
using var decoder = new JpegDecoderCore(option);
using var ms = new MemoryStream(sourceBytes);
using var bufferedStream = new BufferedReadStream(Configuration.Default, ms);
@ -78,8 +79,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
// Calculating data from ImageSharp
byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes;
JpegDecoderOptions options = new();
using var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder());
using var decoder = new JpegDecoderCore(options);
using var ms = new MemoryStream(sourceBytes);
using var bufferedStream = new BufferedReadStream(Configuration.Default, ms);

31
tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs

@ -18,12 +18,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
public class SpectralToPixelConversionTests
{
public static readonly string[] BaselineTestJpegs =
{
TestImages.Jpeg.Baseline.Calliphora, TestImages.Jpeg.Baseline.Cmyk, TestImages.Jpeg.Baseline.Jpeg400,
TestImages.Jpeg.Baseline.Jpeg444, TestImages.Jpeg.Baseline.Testorig420,
TestImages.Jpeg.Baseline.Jpeg420Small, TestImages.Jpeg.Baseline.Bad.BadEOF,
TestImages.Jpeg.Baseline.MultiScanBaselineCMYK
};
{
TestImages.Jpeg.Baseline.Calliphora, TestImages.Jpeg.Baseline.Cmyk, TestImages.Jpeg.Baseline.Jpeg400,
TestImages.Jpeg.Baseline.Jpeg444, TestImages.Jpeg.Baseline.Testorig420,
TestImages.Jpeg.Baseline.Jpeg420Small, TestImages.Jpeg.Baseline.Bad.BadEOF,
TestImages.Jpeg.Baseline.MultiScanBaselineCMYK
};
public SpectralToPixelConversionTests(ITestOutputHelper output) => this.Output = output;
@ -40,8 +40,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
using var bufferedStream = new BufferedReadStream(Configuration.Default, ms);
// Decoding
JpegDecoderOptions options = new();
using var converter = new SpectralConverter<TPixel>(Configuration.Default);
using var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder());
using var decoder = new JpegDecoderCore(options);
var scanDecoder = new HuffmanScanDecoder(bufferedStream, converter, cancellationToken: default);
decoder.ParseStream(bufferedStream, converter, cancellationToken: default);
@ -50,17 +51,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
provider.Utility.TestName = JpegDecoderTests.DecodeBaselineJpegOutputName;
// Comparison
using (var image = new Image<TPixel>(Configuration.Default, converter.GetPixelBuffer(CancellationToken.None), new ImageMetadata()))
using (Image<TPixel> referenceImage = provider.GetReferenceOutputImage<TPixel>(appendPixelTypeToFileName: false))
{
ImageSimilarityReport report = ImageComparer.Exact.CompareImagesOrFrames(referenceImage, image);
using var image = new Image<TPixel>(Configuration.Default, converter.GetPixelBuffer(CancellationToken.None), new ImageMetadata());
using Image<TPixel> referenceImage = provider.GetReferenceOutputImage<TPixel>(appendPixelTypeToFileName: false);
ImageSimilarityReport report = ImageComparer.Exact.CompareImagesOrFrames(referenceImage, image);
this.Output.WriteLine($"*** {provider.SourceFileOrDescription} ***");
this.Output.WriteLine($"Difference: {report.DifferencePercentageString}");
this.Output.WriteLine($"*** {provider.SourceFileOrDescription} ***");
this.Output.WriteLine($"Difference: {report.DifferencePercentageString}");
// ReSharper disable once PossibleInvalidOperationException
Assert.True(report.TotalNormalizedDifference.Value < 0.005f);
}
// ReSharper disable once PossibleInvalidOperationException
Assert.True(report.TotalNormalizedDifference.Value < 0.005f);
}
}
}

11
tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs

@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils
// ReSharper disable once InconsistentNaming
public static float[] Create8x8FloatData()
{
var result = new float[64];
float[] result = new float[64];
for (int i = 0; i < 8; i++)
{
for (int j = 0; j < 8; j++)
@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils
// ReSharper disable once InconsistentNaming
public static int[] Create8x8IntData()
{
var result = new int[64];
int[] result = new int[64];
for (int i = 0; i < 8; i++)
{
for (int j = 0; j < 8; j++)
@ -55,7 +55,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils
// ReSharper disable once InconsistentNaming
public static short[] Create8x8ShortData()
{
var result = new short[64];
short[] result = new short[64];
for (int i = 0; i < 8; i++)
{
for (int j = 0; j < 8; j++)
@ -77,7 +77,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils
public static int[] Create8x8RandomIntData(int minValue, int maxValue, int seed = 42)
{
var rnd = new Random(seed);
var result = new int[64];
int[] result = new int[64];
for (int i = 0; i < 8; i++)
{
for (int j = 0; j < 8; j++)
@ -222,7 +222,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils
using var ms = new MemoryStream(bytes);
using var bufferedStream = new BufferedReadStream(Configuration.Default, ms);
var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder());
JpegDecoderOptions options = new();
var decoder = new JpegDecoderCore(options);
if (metaDataOnly)
{
decoder.Identify(bufferedStream, cancellationToken: default);

25
tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs

@ -1,9 +1,12 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using System.IO;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Pbm;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using Xunit;
using static SixLabors.ImageSharp.Tests.TestImages.Pbm;
@ -97,5 +100,27 @@ namespace SixLabors.ImageSharp.Tests.Formats.Pbm
bool isGrayscale = extension is "pgm" or "pbm";
image.CompareToReferenceOutput(provider, grayscale: isGrayscale);
}
[Theory]
[WithFile(RgbPlain, PixelTypes.Rgb24)]
public void PbmDecoder_Decode_Resize<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
DecoderOptions options = new()
{
TargetSize = new() { Width = 150, Height = 150 }
};
using Image<TPixel> image = provider.GetImage(new PbmDecoder(), options);
FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}";
image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false);
image.CompareToReferenceOutput(
ImageComparer.Exact,
provider,
testOutputDetails: details,
appendPixelTypeToFileName: false);
}
}
}

10
tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs

@ -4,6 +4,7 @@
using System.Buffers.Binary;
using System.IO;
using System.Text;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
@ -75,7 +76,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
var decoder = new PngDecoder();
ImageFormatException exception =
Assert.Throws<InvalidImageContentException>(() => decoder.Decode<Rgb24>(Configuration.Default, memStream, default));
Assert.Throws<InvalidImageContentException>(() => decoder.Decode<Rgb24>(DecoderOptions.Default, memStream));
Assert.Equal($"CRC Error. PNG {chunkName} chunk is corrupt!", exception.Message);
}
@ -83,18 +84,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
private static string GetChunkTypeName(uint value)
{
var data = new byte[4];
byte[] data = new byte[4];
BinaryPrimitives.WriteUInt32BigEndian(data, value);
return Encoding.ASCII.GetString(data);
}
private static void WriteHeaderChunk(MemoryStream memStream)
{
private static void WriteHeaderChunk(MemoryStream memStream) =>
// Writes a 1x1 32bit png header chunk containing a single black pixel.
memStream.Write(Raw1X1PngIhdrAndpHYs, 0, Raw1X1PngIhdrAndpHYs.Length);
}
private static void WriteChunk(MemoryStream memStream, string chunkName)
{

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

@ -5,7 +5,7 @@ using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.DotNet.RemoteExecutor;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -112,6 +112,28 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
image.CompareToOriginal(provider, ImageComparer.Exact);
}
[Theory]
[WithFile(TestImages.Png.Splash, PixelTypes.Rgba32)]
public void PngDecoder_Decode_Resize<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
DecoderOptions options = new()
{
TargetSize = new() { Width = 150, Height = 150 }
};
using Image<TPixel> image = provider.GetImage(PngDecoder, options);
FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}";
image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false);
image.CompareToReferenceOutput(
ImageComparer.TolerantPercentage(0.0003F), // Magick decoder shows difference on Mac
provider,
testOutputDetails: details,
appendPixelTypeToFileName: false);
}
[Theory]
[WithFile(TestImages.Png.AverageFilter3BytesPerPixel, PixelTypes.Rgba32)]
[WithFile(TestImages.Png.AverageFilter4BytesPerPixel, PixelTypes.Rgba32)]

225
tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs

@ -111,15 +111,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
[WithTestPatternImages(nameof(PngColorTypes), 7, 5, PixelTypes.Rgba32)]
public void WorksWithDifferentSizes<TPixel>(TestImageProvider<TPixel> provider, PngColorType pngColorType)
where TPixel : unmanaged, IPixel<TPixel>
{
TestPngEncoderCore(
=> TestPngEncoderCore(
provider,
pngColorType,
PngFilterMethod.Adaptive,
PngBitDepth.Bit8,
PngInterlaceMode.None,
appendPngColorType: true);
}
[Theory]
[WithTestPatternImages(nameof(PngColorTypes), 24, 24, PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24)]
@ -199,7 +197,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
return;
}
foreach (var filterMethod in PngFilterMethods)
foreach (object[] filterMethod in PngFilterMethods)
{
foreach (PngInterlaceMode interlaceMode in InterlaceMode)
{
@ -235,7 +233,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
public void WorksWithAllBitDepthsAndExcludeAllFilter<TPixel>(TestImageProvider<TPixel> provider, PngColorType pngColorType, PngBitDepth pngBitDepth)
where TPixel : unmanaged, IPixel<TPixel>
{
foreach (var filterMethod in PngFilterMethods)
foreach (object[] filterMethod in PngFilterMethods)
{
foreach (PngInterlaceMode interlaceMode in InterlaceMode)
{
@ -284,20 +282,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
public void InfersColorTypeAndBitDepth<TPixel>(TestImageProvider<TPixel> provider, PngColorType pngColorType, PngBitDepth pngBitDepth)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Stream stream = new MemoryStream())
{
PngEncoder.Encode(provider.GetImage(), stream);
using Stream stream = new MemoryStream();
PngEncoder.Encode(provider.GetImage(), stream);
stream.Seek(0, SeekOrigin.Begin);
stream.Seek(0, SeekOrigin.Begin);
var decoder = new PngDecoder();
var decoder = new PngDecoder();
Image image = decoder.Decode(Configuration.Default, stream, default);
Image image = decoder.Decode(DecoderOptions.Default, stream);
PngMetadata metadata = image.Metadata.GetPngMetadata();
Assert.Equal(pngColorType, metadata.ColorType);
Assert.Equal(pngBitDepth, metadata.BitDepth);
}
PngMetadata metadata = image.Metadata.GetPngMetadata();
Assert.Equal(pngColorType, metadata.ColorType);
Assert.Equal(pngBitDepth, metadata.BitDepth);
}
[Theory]
@ -329,14 +325,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
public void WritesFileMarker<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
using (var ms = new MemoryStream())
{
image.Save(ms, PngEncoder);
using Image<TPixel> image = provider.GetImage();
using var ms = new MemoryStream();
image.Save(ms, PngEncoder);
byte[] data = ms.ToArray().Take(8).ToArray();
byte[] expected =
{
byte[] data = ms.ToArray().Take(8).ToArray();
byte[] expected =
{
0x89, // Set the high bit.
0x50, // P
0x4E, // N
@ -347,8 +342,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
0x0A // LF
};
Assert.Equal(expected, data);
}
Assert.Equal(expected, data);
}
[Theory]
@ -356,22 +350,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{
var testFile = TestFile.Create(imagePath);
using (Image<Rgba32> input = testFile.CreateRgba32Image())
{
using (var memStream = new MemoryStream())
{
input.Save(memStream, PngEncoder);
using Image<Rgba32> input = testFile.CreateRgba32Image();
using var memStream = new MemoryStream();
input.Save(memStream, PngEncoder);
memStream.Position = 0;
using (var output = Image.Load<Rgba32>(memStream))
{
ImageMetadata meta = output.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
}
}
memStream.Position = 0;
using var output = Image.Load<Rgba32>(memStream);
ImageMetadata meta = output.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
[Theory]
@ -379,21 +367,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
public void Encode_PreserveBits(string imagePath, PngBitDepth pngBitDepth)
{
var testFile = TestFile.Create(imagePath);
using (Image<Rgba32> input = testFile.CreateRgba32Image())
{
using (var memStream = new MemoryStream())
{
input.Save(memStream, PngEncoder);
using Image<Rgba32> input = testFile.CreateRgba32Image();
using var memStream = new MemoryStream();
input.Save(memStream, PngEncoder);
memStream.Position = 0;
using (var output = Image.Load<Rgba32>(memStream))
{
PngMetadata meta = output.Metadata.GetPngMetadata();
memStream.Position = 0;
using var output = Image.Load<Rgba32>(memStream);
PngMetadata meta = output.Metadata.GetPngMetadata();
Assert.Equal(pngBitDepth, meta.BitDepth);
}
}
}
Assert.Equal(pngBitDepth, meta.BitDepth);
}
[Theory]
@ -437,9 +419,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
memStream.Position = 0;
using var actual = Image.Load<Rgba32>(memStream);
Rgba32 expectedColor = Color.Blue;
if (colorType == PngColorType.Grayscale || colorType == PngColorType.GrayscaleWithAlpha)
if (colorType is PngColorType.Grayscale or PngColorType.GrayscaleWithAlpha)
{
var luminance = ColorNumerics.Get8BitBT709Luminance(expectedColor.R, expectedColor.G, expectedColor.B);
byte luminance = ColorNumerics.Get8BitBT709Luminance(expectedColor.R, expectedColor.G, expectedColor.B);
expectedColor = new Rgba32(luminance, luminance, luminance);
}
@ -467,51 +449,45 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
public void Encode_PreserveTrns(string imagePath, PngBitDepth pngBitDepth, PngColorType pngColorType)
{
var testFile = TestFile.Create(imagePath);
using (Image<Rgba32> input = testFile.CreateRgba32Image())
using Image<Rgba32> input = testFile.CreateRgba32Image();
PngMetadata inMeta = input.Metadata.GetPngMetadata();
Assert.True(inMeta.HasTransparency);
using var memStream = new MemoryStream();
input.Save(memStream, PngEncoder);
memStream.Position = 0;
using var output = Image.Load<Rgba32>(memStream);
PngMetadata outMeta = output.Metadata.GetPngMetadata();
Assert.True(outMeta.HasTransparency);
switch (pngColorType)
{
PngMetadata inMeta = input.Metadata.GetPngMetadata();
Assert.True(inMeta.HasTransparency);
case PngColorType.Grayscale:
if (pngBitDepth.Equals(PngBitDepth.Bit16))
{
Assert.True(outMeta.TransparentL16.HasValue);
Assert.Equal(inMeta.TransparentL16, outMeta.TransparentL16);
}
else
{
Assert.True(outMeta.TransparentL8.HasValue);
Assert.Equal(inMeta.TransparentL8, outMeta.TransparentL8);
}
using (var memStream = new MemoryStream())
{
input.Save(memStream, PngEncoder);
memStream.Position = 0;
using (var output = Image.Load<Rgba32>(memStream))
break;
case PngColorType.Rgb:
if (pngBitDepth.Equals(PngBitDepth.Bit16))
{
PngMetadata outMeta = output.Metadata.GetPngMetadata();
Assert.True(outMeta.HasTransparency);
switch (pngColorType)
{
case PngColorType.Grayscale:
if (pngBitDepth.Equals(PngBitDepth.Bit16))
{
Assert.True(outMeta.TransparentL16.HasValue);
Assert.Equal(inMeta.TransparentL16, outMeta.TransparentL16);
}
else
{
Assert.True(outMeta.TransparentL8.HasValue);
Assert.Equal(inMeta.TransparentL8, outMeta.TransparentL8);
}
break;
case PngColorType.Rgb:
if (pngBitDepth.Equals(PngBitDepth.Bit16))
{
Assert.True(outMeta.TransparentRgb48.HasValue);
Assert.Equal(inMeta.TransparentRgb48, outMeta.TransparentRgb48);
}
else
{
Assert.True(outMeta.TransparentRgb24.HasValue);
Assert.Equal(inMeta.TransparentRgb24, outMeta.TransparentRgb24);
}
break;
}
Assert.True(outMeta.TransparentRgb48.HasValue);
Assert.Equal(inMeta.TransparentRgb48, outMeta.TransparentRgb48);
}
}
else
{
Assert.True(outMeta.TransparentRgb24.HasValue);
Assert.Equal(inMeta.TransparentRgb24, outMeta.TransparentRgb24);
}
break;
}
}
@ -591,41 +567,42 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
PngChunkFilter optimizeMethod = PngChunkFilter.None)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
using Image<TPixel> image = provider.GetImage();
var encoder = new PngEncoder
{
var encoder = new PngEncoder
{
ColorType = pngColorType,
FilterMethod = pngFilterMethod,
CompressionLevel = compressionLevel,
BitDepth = bitDepth,
Quantizer = new WuQuantizer(new QuantizerOptions { MaxColors = paletteSize }),
InterlaceMethod = interlaceMode,
ChunkFilter = optimizeMethod,
};
ColorType = pngColorType,
FilterMethod = pngFilterMethod,
CompressionLevel = compressionLevel,
BitDepth = bitDepth,
Quantizer = new WuQuantizer(new QuantizerOptions { MaxColors = paletteSize }),
InterlaceMethod = interlaceMode,
ChunkFilter = optimizeMethod,
};
string pngColorTypeInfo = appendPngColorType ? pngColorType.ToString() : string.Empty;
string pngFilterMethodInfo = appendPngFilterMethod ? pngFilterMethod.ToString() : string.Empty;
string compressionLevelInfo = appendCompressionLevel ? $"_C{compressionLevel}" : string.Empty;
string paletteSizeInfo = appendPaletteSize ? $"_PaletteSize-{paletteSize}" : string.Empty;
string pngBitDepthInfo = appendPngBitDepth ? bitDepth.ToString() : string.Empty;
string pngInterlaceModeInfo = interlaceMode != PngInterlaceMode.None ? $"_{interlaceMode}" : string.Empty;
string pngColorTypeInfo = appendPngColorType ? pngColorType.ToString() : string.Empty;
string pngFilterMethodInfo = appendPngFilterMethod ? pngFilterMethod.ToString() : string.Empty;
string compressionLevelInfo = appendCompressionLevel ? $"_C{compressionLevel}" : string.Empty;
string paletteSizeInfo = appendPaletteSize ? $"_PaletteSize-{paletteSize}" : string.Empty;
string pngBitDepthInfo = appendPngBitDepth ? bitDepth.ToString() : string.Empty;
string pngInterlaceModeInfo = interlaceMode != PngInterlaceMode.None ? $"_{interlaceMode}" : string.Empty;
string debugInfo = $"{pngColorTypeInfo}{pngFilterMethodInfo}{compressionLevelInfo}{paletteSizeInfo}{pngBitDepthInfo}{pngInterlaceModeInfo}";
string debugInfo = $"{pngColorTypeInfo}{pngFilterMethodInfo}{compressionLevelInfo}{paletteSizeInfo}{pngBitDepthInfo}{pngInterlaceModeInfo}";
string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "png", encoder, debugInfo, appendPixelType);
string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "png", encoder, debugInfo, appendPixelType);
IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile);
// Compare to the Magick reference decoder.
IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile);
// We compare using both our decoder and the reference decoder as pixel transformation
// occurs within the encoder itself leaving the input image unaffected.
// This means we are benefiting from testing our decoder also.
using (var imageSharpImage = Image.Load<TPixel>(actualOutputFile, new PngDecoder()))
using (var referenceImage = Image.Load<TPixel>(actualOutputFile, referenceDecoder))
{
ImageComparer.Exact.VerifySimilarity(referenceImage, imageSharpImage);
}
}
// We compare using both our decoder and the reference decoder as pixel transformation
// occurs within the encoder itself leaving the input image unaffected.
// This means we are benefiting from testing our decoder also.
using FileStream fileStream = File.OpenRead(actualOutputFile);
using Image<TPixel> imageSharpImage = new PngDecoder().Decode<TPixel>(DecoderOptions.Default, fileStream);
fileStream.Position = 0;
using Image<TPixel> referenceImage = referenceDecoder.Decode<TPixel>(DecoderOptions.Default, fileStream, default);
ImageComparer.Exact.VerifySimilarity(referenceImage, imageSharpImage);
}
}
}

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

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
@ -56,11 +57,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
public void Decoder_CanReadTextData<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new PngDecoder()))
{
PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance);
VerifyTextDataIsPresent(meta);
}
using Image<TPixel> image = provider.GetImage(new PngDecoder());
PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance);
VerifyTextDataIsPresent(meta);
}
[Theory]
@ -69,18 +68,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
where TPixel : unmanaged, IPixel<TPixel>
{
var decoder = new PngDecoder();
using (Image<TPixel> input = provider.GetImage(decoder))
using (var memoryStream = new MemoryStream())
{
input.Save(memoryStream, new PngEncoder());
memoryStream.Position = 0;
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, memoryStream, default))
{
PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance);
VerifyTextDataIsPresent(meta);
}
}
using Image<TPixel> input = provider.GetImage(decoder);
using var memoryStream = new MemoryStream();
input.Save(memoryStream, new PngEncoder());
memoryStream.Position = 0;
using Image<Rgba32> image = decoder.Decode<Rgba32>(DecoderOptions.Default, memoryStream);
PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance);
VerifyTextDataIsPresent(meta);
}
[Theory]
@ -88,16 +83,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
public void Decoder_IgnoresInvalidTextData<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new PngDecoder()))
{
PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance);
Assert.DoesNotContain(meta.TextData, m => m.Value is "leading space");
Assert.DoesNotContain(meta.TextData, m => m.Value is "trailing space");
Assert.DoesNotContain(meta.TextData, m => m.Value is "space");
Assert.DoesNotContain(meta.TextData, m => m.Value is "empty");
Assert.DoesNotContain(meta.TextData, m => m.Value is "invalid characters");
Assert.DoesNotContain(meta.TextData, m => m.Value is "too large");
}
using Image<TPixel> image = provider.GetImage(new PngDecoder());
PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance);
Assert.DoesNotContain(meta.TextData, m => m.Value is "leading space");
Assert.DoesNotContain(meta.TextData, m => m.Value is "trailing space");
Assert.DoesNotContain(meta.TextData, m => m.Value is "space");
Assert.DoesNotContain(meta.TextData, m => m.Value is "empty");
Assert.DoesNotContain(meta.TextData, m => m.Value is "invalid characters");
Assert.DoesNotContain(meta.TextData, m => m.Value is "too large");
}
[Theory]
@ -106,30 +99,27 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
where TPixel : unmanaged, IPixel<TPixel>
{
var decoder = new PngDecoder();
using (Image<TPixel> input = provider.GetImage(decoder))
using (var memoryStream = new MemoryStream())
using Image<TPixel> input = provider.GetImage(decoder);
using var memoryStream = new MemoryStream();
// This will be a zTXt chunk.
var expectedText = new PngTextData("large-text", new string('c', 100), string.Empty, string.Empty);
// This will be a iTXt chunk.
var expectedTextNoneLatin = new PngTextData("large-text-non-latin", new string('Ф', 100), "language-tag", "translated-keyword");
PngMetadata inputMetadata = input.Metadata.GetFormatMetadata(PngFormat.Instance);
inputMetadata.TextData.Add(expectedText);
inputMetadata.TextData.Add(expectedTextNoneLatin);
input.Save(memoryStream, new PngEncoder
{
// This will be a zTXt chunk.
var expectedText = new PngTextData("large-text", new string('c', 100), string.Empty, string.Empty);
// This will be a iTXt chunk.
var expectedTextNoneLatin = new PngTextData("large-text-non-latin", new string('Ф', 100), "language-tag", "translated-keyword");
PngMetadata inputMetadata = input.Metadata.GetFormatMetadata(PngFormat.Instance);
inputMetadata.TextData.Add(expectedText);
inputMetadata.TextData.Add(expectedTextNoneLatin);
input.Save(memoryStream, new PngEncoder
{
TextCompressionThreshold = 50
});
memoryStream.Position = 0;
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, memoryStream, default))
{
PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance);
Assert.Contains(meta.TextData, m => m.Equals(expectedText));
Assert.Contains(meta.TextData, m => m.Equals(expectedTextNoneLatin));
}
}
TextCompressionThreshold = 50
});
memoryStream.Position = 0;
using Image<Rgba32> image = decoder.Decode<Rgba32>(DecoderOptions.Default, memoryStream);
PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance);
Assert.Contains(meta.TextData, m => m.Equals(expectedText));
Assert.Contains(meta.TextData, m => m.Equals(expectedTextNoneLatin));
}
[Theory]
@ -137,17 +127,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
public void Decode_ReadsExifData<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
var decoder = new PngDecoder
DecoderOptions options = new()
{
IgnoreMetadata = false
SkipMetadata = false
};
using (Image<TPixel> image = provider.GetImage(decoder))
{
Assert.NotNull(image.Metadata.ExifProfile);
ExifProfile exif = image.Metadata.ExifProfile;
VerifyExifDataIsPresent(exif);
}
using Image<TPixel> image = provider.GetImage(new PngDecoder(), options);
Assert.NotNull(image.Metadata.ExifProfile);
ExifProfile exif = image.Metadata.ExifProfile;
VerifyExifDataIsPresent(exif);
}
[Theory]
@ -155,53 +143,49 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
public void Decode_IgnoresExifData_WhenIgnoreMetadataIsTrue<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
var decoder = new PngDecoder
DecoderOptions options = new()
{
IgnoreMetadata = true
SkipMetadata = true
};
using (Image<TPixel> image = provider.GetImage(decoder))
{
Assert.Null(image.Metadata.ExifProfile);
}
PngDecoder decoder = new();
using Image<TPixel> image = provider.GetImage(decoder, options);
Assert.Null(image.Metadata.ExifProfile);
}
[Fact]
public void Decode_IgnoreMetadataIsFalse_TextChunkIsRead()
{
var options = new PngDecoder
DecoderOptions options = new()
{
IgnoreMetadata = false
SkipMetadata = false
};
var testFile = TestFile.Create(TestImages.Png.Blur);
using (Image<Rgba32> image = testFile.CreateRgba32Image(options))
{
PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance);
using Image<Rgba32> image = testFile.CreateRgba32Image(new PngDecoder(), options);
PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance);
Assert.Equal(1, meta.TextData.Count);
Assert.Equal("Software", meta.TextData[0].Keyword);
Assert.Equal("paint.net 4.0.6", meta.TextData[0].Value);
Assert.Equal(0.4545d, meta.Gamma, precision: 4);
}
Assert.Equal(1, meta.TextData.Count);
Assert.Equal("Software", meta.TextData[0].Keyword);
Assert.Equal("paint.net 4.0.6", meta.TextData[0].Value);
Assert.Equal(0.4545d, meta.Gamma, precision: 4);
}
[Fact]
public void Decode_IgnoreMetadataIsTrue_TextChunksAreIgnored()
{
var options = new PngDecoder
DecoderOptions options = new()
{
IgnoreMetadata = true
SkipMetadata = true
};
var testFile = TestFile.Create(TestImages.Png.PngWithMetadata);
using (Image<Rgba32> image = testFile.CreateRgba32Image(options))
{
PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance);
Assert.Equal(0, meta.TextData.Count);
}
using Image<Rgba32> image = testFile.CreateRgba32Image(new PngDecoder(), options);
PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance);
Assert.Equal(0, meta.TextData.Count);
}
[Theory]
@ -209,17 +193,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
var decoder = new PngDecoder();
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, stream, default))
{
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
}
using var stream = new MemoryStream(testFile.Bytes, false);
var decoder = new PngDecoder();
using Image<Rgba32> image = decoder.Decode<Rgba32>(DecoderOptions.Default, stream);
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
[Theory]
@ -227,26 +207,20 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
public void Encode_PreservesColorProfile<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> input = provider.GetImage(new PngDecoder()))
{
ImageSharp.Metadata.Profiles.Icc.IccProfile expectedProfile = input.Metadata.IccProfile;
byte[] expectedProfileBytes = expectedProfile.ToByteArray();
using (var memStream = new MemoryStream())
{
input.Save(memStream, new PngEncoder());
memStream.Position = 0;
using (var output = Image.Load<Rgba32>(memStream))
{
ImageSharp.Metadata.Profiles.Icc.IccProfile actualProfile = output.Metadata.IccProfile;
byte[] actualProfileBytes = actualProfile.ToByteArray();
Assert.NotNull(actualProfile);
Assert.Equal(expectedProfileBytes, actualProfileBytes);
}
}
}
using Image<TPixel> input = provider.GetImage(new PngDecoder());
ImageSharp.Metadata.Profiles.Icc.IccProfile expectedProfile = input.Metadata.IccProfile;
byte[] expectedProfileBytes = expectedProfile.ToByteArray();
using var memStream = new MemoryStream();
input.Save(memStream, new PngEncoder());
memStream.Position = 0;
using var output = Image.Load<Rgba32>(memStream);
ImageSharp.Metadata.Profiles.Icc.IccProfile actualProfile = output.Metadata.IccProfile;
byte[] actualProfileBytes = actualProfile.ToByteArray();
Assert.NotNull(actualProfile);
Assert.Equal(expectedProfileBytes, actualProfileBytes);
}
[Theory]
@ -254,15 +228,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
var decoder = new PngDecoder();
IImageInfo image = decoder.Identify(Configuration.Default, stream, default);
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
using var stream = new MemoryStream(testFile.Bytes, false);
var decoder = new PngDecoder();
IImageInfo image = decoder.Identify(DecoderOptions.Default, stream);
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
[Theory]
@ -270,13 +242,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
public void Identify_ReadsTextData(string imagePath)
{
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
IImageInfo imageInfo = Image.Identify(stream);
Assert.NotNull(imageInfo);
PngMetadata meta = imageInfo.Metadata.GetFormatMetadata(PngFormat.Instance);
VerifyTextDataIsPresent(meta);
}
using var stream = new MemoryStream(testFile.Bytes, false);
IImageInfo imageInfo = Image.Identify(stream);
Assert.NotNull(imageInfo);
PngMetadata meta = imageInfo.Metadata.GetFormatMetadata(PngFormat.Instance);
VerifyTextDataIsPresent(meta);
}
[Theory]
@ -284,14 +254,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
public void Identify_ReadsExifData(string imagePath)
{
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
IImageInfo imageInfo = Image.Identify(stream);
Assert.NotNull(imageInfo);
Assert.NotNull(imageInfo.Metadata.ExifProfile);
ExifProfile exif = imageInfo.Metadata.ExifProfile;
VerifyExifDataIsPresent(exif);
}
using var stream = new MemoryStream(testFile.Bytes, false);
IImageInfo imageInfo = Image.Identify(stream);
Assert.NotNull(imageInfo);
Assert.NotNull(imageInfo.Metadata.ExifProfile);
ExifProfile exif = imageInfo.Metadata.ExifProfile;
VerifyExifDataIsPresent(exif);
}
private static void VerifyExifDataIsPresent(ExifProfile exif)
@ -323,28 +291,26 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
public void Identify_ReadsLegacyExifData(string imagePath)
{
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
IImageInfo imageInfo = Image.Identify(stream);
Assert.NotNull(imageInfo);
Assert.NotNull(imageInfo.Metadata.ExifProfile);
PngMetadata meta = imageInfo.Metadata.GetFormatMetadata(PngFormat.Instance);
Assert.DoesNotContain(meta.TextData, t => t.Keyword.Equals("Raw profile type exif", StringComparison.OrdinalIgnoreCase));
ExifProfile exif = imageInfo.Metadata.ExifProfile;
Assert.Equal(0, exif.InvalidTags.Count);
Assert.Equal(3, exif.Values.Count);
Assert.Equal(
"A colorful tiling of blue, red, yellow, and green 4x4 pixel blocks.",
exif.GetValue(ExifTag.ImageDescription).Value);
Assert.Equal(
"Duplicated from basn3p02.png, then image metadata modified with exiv2",
exif.GetValue(ExifTag.ImageHistory).Value);
Assert.Equal(42, (int)exif.GetValue(ExifTag.ImageNumber).Value);
}
using var stream = new MemoryStream(testFile.Bytes, false);
IImageInfo imageInfo = Image.Identify(stream);
Assert.NotNull(imageInfo);
Assert.NotNull(imageInfo.Metadata.ExifProfile);
PngMetadata meta = imageInfo.Metadata.GetFormatMetadata(PngFormat.Instance);
Assert.DoesNotContain(meta.TextData, t => t.Keyword.Equals("Raw profile type exif", StringComparison.OrdinalIgnoreCase));
ExifProfile exif = imageInfo.Metadata.ExifProfile;
Assert.Equal(0, exif.InvalidTags.Count);
Assert.Equal(3, exif.Values.Count);
Assert.Equal(
"A colorful tiling of blue, red, yellow, and green 4x4 pixel blocks.",
exif.GetValue(ExifTag.ImageDescription).Value);
Assert.Equal(
"Duplicated from basn3p02.png, then image metadata modified with exiv2",
exif.GetValue(ExifTag.ImageHistory).Value);
Assert.Equal(42, (int)exif.GetValue(ExifTag.ImageNumber).Value);
}
}
}

109
tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs

@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License.
using System.IO;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
@ -19,84 +20,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
where TPixel : unmanaged, IPixel<TPixel>
{
// does saving a file then reopening mean both files are identical???
using (Image<TPixel> image = provider.GetImage())
using (var ms = new MemoryStream())
{
// image.Save(provider.Utility.GetTestOutputFileName("bmp"));
image.Save(ms, new PngEncoder());
ms.Position = 0;
using (var img2 = Image.Load<Rgba32>(ms, new PngDecoder()))
{
ImageComparer.Tolerant().VerifySimilarity(image, img2);
using Image<TPixel> image = provider.GetImage();
using var ms = new MemoryStream();
// img2.Save(provider.Utility.GetTestOutputFileName("bmp", "_loaded"), new BmpEncoder());
}
}
}
/* JJS: Disabled for now as the decoder now correctly decodes the full pixel components if the
paletted image has alpha of 0
[Theory]
[WithTestPatternImages(100, 100, PixelTypes.Rgba32)]
public void CanSaveIndexedPng<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
// does saving a file then reopening mean both files are identical???
using (Image<TPixel> image = provider.GetImage())
using (MemoryStream ms = new MemoryStream())
{
// image.Save(provider.Utility.GetTestOutputFileName("bmp"));
image.Save(ms, new PngEncoder() { PaletteSize = 256 });
ms.Position = 0;
using (Image<Rgba32> img2 = Image.Load<Rgba32>(ms, new PngDecoder()))
{
ImageComparer.VerifySimilarity(image, img2, 0.03f);
}
}
}*/
// image.Save(provider.Utility.GetTestOutputFileName("bmp"));
image.Save(ms, new PngEncoder());
ms.Position = 0;
using Image<Rgba32> img2 = new PngDecoder().Decode<Rgba32>(DecoderOptions.Default, ms);
ImageComparer.Tolerant().VerifySimilarity(image, img2);
/* JJS: Commented out for now since the test does not take into lossy nature of indexing.
[Theory]
[WithTestPatternImages(100, 100, PixelTypes.Color)]
public void CanSaveIndexedPngTwice<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
// does saving a file then reopening mean both files are identical???
using (Image<TPixel> source = provider.GetImage())
using (MemoryStream ms = new MemoryStream())
{
source.Metadata.Quality = 256;
source.Save(ms, new PngEncoder(), new PngEncoderOptions {
Threshold = 200
});
ms.Position = 0;
using (Image img1 = Image.Load(ms, new PngDecoder()))
{
using (MemoryStream ms2 = new MemoryStream())
{
img1.Save(ms2, new PngEncoder(), new PngEncoderOptions
{
Threshold = 200
});
ms2.Position = 0;
using (Image img2 = Image.Load(ms2, new PngDecoder()))
{
using (PixelAccessor<Color> pixels1 = img1.Lock())
using (PixelAccessor<Color> pixels2 = img2.Lock())
{
for (int y = 0; y < img1.Height; y++)
{
for (int x = 0; x < img1.Width; x++)
{
Assert.Equal(pixels1[x, y], pixels2[x, y]);
}
}
}
}
}
}
}
}*/
// img2.Save(provider.Utility.GetTestOutputFileName("bmp", "_loaded"), new BmpEncoder());
}
[Theory]
[WithTestPatternImages(300, 300, PixelTypes.Rgba32)]
@ -104,20 +38,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
where TPixel : unmanaged, IPixel<TPixel>
{
// does saving a file then reopening mean both files are identical???
using (Image<TPixel> image = provider.GetImage())
using (var ms = new MemoryStream())
{
// image.Save(provider.Utility.GetTestOutputFileName("png"));
image.Mutate(x => x.Resize(100, 100));
using Image<TPixel> image = provider.GetImage();
using var ms = new MemoryStream();
// image.Save(provider.Utility.GetTestOutputFileName("png"));
image.Mutate(x => x.Resize(100, 100));
// image.Save(provider.Utility.GetTestOutputFileName("png", "resize"));
image.Save(ms, new PngEncoder());
ms.Position = 0;
using (var img2 = Image.Load<Rgba32>(ms, new PngDecoder()))
{
ImageComparer.Tolerant().VerifySimilarity(image, img2);
}
}
// image.Save(provider.Utility.GetTestOutputFileName("png", "resize"));
image.Save(ms, new PngEncoder());
ms.Position = 0;
using Image<Rgba32> img2 = new PngDecoder().Decode<Rgba32>(DecoderOptions.Default, ms);
ImageComparer.Tolerant().VerifySimilarity(image, img2);
}
}
}

25
tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs

@ -1,8 +1,9 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using Microsoft.DotNet.RemoteExecutor;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -747,6 +748,28 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga
}
}
[Theory]
[WithFile(Bit32RleTopLeft, PixelTypes.Rgba32)]
public void TgaDecoder_Decode_Resize<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
DecoderOptions options = new()
{
TargetSize = new() { Width = 150, Height = 150 }
};
using Image<TPixel> image = provider.GetImage(TgaDecoder, options);
FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}";
image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false);
image.CompareToReferenceOutput(
ImageComparer.TolerantPercentage(0.0001F),
provider,
testOutputDetails: details,
appendPixelTypeToFileName: false);
}
[Theory]
[WithFile(Bit16BottomLeft, PixelTypes.Rgba32)]
[WithFile(Bit24BottomLeft, PixelTypes.Rgba32)]

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

@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga
Assert.Throws<UnknownImageFormatException>(() =>
{
using (Image.Load(Configuration.Default, stream, out IImageFormat _))
using (Image.Load(DecoderOptions.Default, stream, out IImageFormat _))
{
}
});

12
tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs

@ -23,16 +23,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression
[InlineData(new byte[] { 1, 2, 42, 53, 42, 53, 42, 53, 42, 53, 42, 53, 3, 4 })] // Repeated sequence
public void Compress_Decompress_Roundtrip_Works(byte[] data)
{
using (BufferedReadStream stream = CreateCompressedStream(data))
{
var buffer = new byte[data.Length];
using BufferedReadStream stream = CreateCompressedStream(data);
byte[] buffer = new byte[data.Length];
using var decompressor = new DeflateTiffCompression(Configuration.Default.MemoryAllocator, 10, 8, TiffColorType.BlackIsZero8, TiffPredictor.None, false);
using var decompressor = new DeflateTiffCompression(Configuration.Default.MemoryAllocator, 10, 8, TiffColorType.BlackIsZero8, TiffPredictor.None, false);
decompressor.Decompress(stream, 0, (uint)stream.Length, 1, buffer);
decompressor.Decompress(stream, 0, (uint)stream.Length, 1, buffer, default);
Assert.Equal(data, buffer);
}
Assert.Equal(data, buffer);
}
private static BufferedReadStream CreateCompressedStream(byte[] data)

6
tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs

@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression
public void Compress_Works(byte[] inputData, byte[] expectedCompressedData)
{
var compressedData = new byte[expectedCompressedData.Length];
byte[] compressedData = new byte[expectedCompressedData.Length];
Stream streamData = CreateCompressedStream(inputData);
streamData.Read(compressedData, 0, expectedCompressedData.Length);
@ -37,10 +37,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression
public void Compress_Decompress_Roundtrip_Works(byte[] data)
{
using BufferedReadStream stream = CreateCompressedStream(data);
var buffer = new byte[data.Length];
byte[] buffer = new byte[data.Length];
using var decompressor = new LzwTiffCompression(Configuration.Default.MemoryAllocator, 10, 8, TiffColorType.BlackIsZero8, TiffPredictor.None, false);
decompressor.Decompress(stream, 0, (uint)stream.Length, 1, buffer);
decompressor.Decompress(stream, 0, (uint)stream.Length, 1, buffer, default);
Assert.Equal(data, buffer);
}

2
tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs

@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression
byte[] buffer = new byte[expectedResult.Length];
using var decompressor = new NoneTiffCompression(default, default, default);
decompressor.Decompress(stream, 0, byteCount, 1, buffer);
decompressor.Decompress(stream, 0, byteCount, 1, buffer, default);
Assert.Equal(expectedResult, buffer);
}

2
tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs

@ -31,7 +31,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression
byte[] buffer = new byte[expectedResult.Length];
using var decompressor = new PackBitsTiffCompression(MemoryAllocator.Create(), default, default);
decompressor.Decompress(stream, 0, (uint)inputData.Length, 1, buffer);
decompressor.Decompress(stream, 0, (uint)inputData.Length, 1, buffer, default);
Assert.Equal(expectedResult, buffer);
}

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

@ -4,6 +4,8 @@
// ReSharper disable InconsistentNaming
using System;
using System.IO;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
@ -649,10 +651,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public void CanDecodeJustOneFrame<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new TiffDecoder() { DecodingMode = FrameDecodingMode.First }))
{
Assert.Equal(1, image.Frames.Count);
}
DecoderOptions options = new() { MaxFrames = 1 };
using Image<TPixel> image = provider.GetImage(new TiffDecoder(), options);
Assert.Equal(1, image.Frames.Count);
}
[Theory]
@ -710,5 +711,27 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
image.DebugSaveMultiFrame(provider);
image.CompareToOriginalMultiFrame(provider, ImageComparer.Exact, ReferenceDecoder);
}
[Theory]
[WithFile(Rgba3BitUnassociatedAlpha, PixelTypes.Rgba32)]
public void TiffDecoder_Decode_Resize<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
DecoderOptions options = new()
{
TargetSize = new() { Width = 150, Height = 150 }
};
using Image<TPixel> image = provider.GetImage(TiffDecoder, options);
FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}";
image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false);
image.CompareToReferenceOutput(
ImageComparer.Exact,
provider,
testOutputDetails: details,
appendPixelTypeToFileName: false);
}
}
}

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

@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Metadata;
@ -50,24 +51,22 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public void TiffFrameMetadata_CloneIsDeep<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(TiffDecoder))
{
TiffFrameMetadata meta = image.Frames.RootFrame.Metadata.GetTiffMetadata();
var cloneSameAsSampleMetaData = (TiffFrameMetadata)meta.DeepClone();
VerifyExpectedTiffFrameMetaDataIsPresent(cloneSameAsSampleMetaData);
using Image<TPixel> image = provider.GetImage(TiffDecoder);
TiffFrameMetadata meta = image.Frames.RootFrame.Metadata.GetTiffMetadata();
var cloneSameAsSampleMetaData = (TiffFrameMetadata)meta.DeepClone();
VerifyExpectedTiffFrameMetaDataIsPresent(cloneSameAsSampleMetaData);
var clone = (TiffFrameMetadata)meta.DeepClone();
var clone = (TiffFrameMetadata)meta.DeepClone();
clone.BitsPerPixel = TiffBitsPerPixel.Bit8;
clone.Compression = TiffCompression.None;
clone.PhotometricInterpretation = TiffPhotometricInterpretation.CieLab;
clone.Predictor = TiffPredictor.Horizontal;
clone.BitsPerPixel = TiffBitsPerPixel.Bit8;
clone.Compression = TiffCompression.None;
clone.PhotometricInterpretation = TiffPhotometricInterpretation.CieLab;
clone.Predictor = TiffPredictor.Horizontal;
Assert.False(meta.BitsPerPixel == clone.BitsPerPixel);
Assert.False(meta.Compression == clone.Compression);
Assert.False(meta.PhotometricInterpretation == clone.PhotometricInterpretation);
Assert.False(meta.Predictor == clone.Predictor);
}
Assert.False(meta.BitsPerPixel == clone.BitsPerPixel);
Assert.False(meta.Compression == clone.Compression);
Assert.False(meta.PhotometricInterpretation == clone.PhotometricInterpretation);
Assert.False(meta.Predictor == clone.Predictor);
}
private static void VerifyExpectedTiffFrameMetaDataIsPresent(TiffFrameMetadata frameMetaData)
@ -119,23 +118,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public void MetadataProfiles<TPixel>(TestImageProvider<TPixel> provider, bool ignoreMetadata)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new TiffDecoder() { IgnoreMetadata = ignoreMetadata }))
DecoderOptions options = new() { SkipMetadata = ignoreMetadata };
using Image<TPixel> image = provider.GetImage(new TiffDecoder(), options);
TiffMetadata meta = image.Metadata.GetTiffMetadata();
ImageFrameMetadata rootFrameMetaData = image.Frames.RootFrame.Metadata;
Assert.NotNull(meta);
if (ignoreMetadata)
{
Assert.Null(rootFrameMetaData.XmpProfile);
Assert.Null(rootFrameMetaData.ExifProfile);
}
else
{
TiffMetadata meta = image.Metadata.GetTiffMetadata();
ImageFrameMetadata rootFrameMetaData = image.Frames.RootFrame.Metadata;
Assert.NotNull(meta);
if (ignoreMetadata)
{
Assert.Null(rootFrameMetaData.XmpProfile);
Assert.Null(rootFrameMetaData.ExifProfile);
}
else
{
Assert.NotNull(rootFrameMetaData.XmpProfile);
Assert.NotNull(rootFrameMetaData.ExifProfile);
Assert.Equal(2599, rootFrameMetaData.XmpProfile.Data.Length);
Assert.Equal(26, rootFrameMetaData.ExifProfile.Values.Count);
}
Assert.NotNull(rootFrameMetaData.XmpProfile);
Assert.NotNull(rootFrameMetaData.ExifProfile);
Assert.Equal(2599, rootFrameMetaData.XmpProfile.Data.Length);
Assert.Equal(26, rootFrameMetaData.ExifProfile.Values.Count);
}
}
@ -158,63 +157,61 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public void BaselineTags<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(TiffDecoder))
{
ImageFrame<TPixel> rootFrame = image.Frames.RootFrame;
Assert.Equal(32, rootFrame.Width);
Assert.Equal(32, rootFrame.Height);
Assert.NotNull(rootFrame.Metadata.XmpProfile);
Assert.Equal(2599, rootFrame.Metadata.XmpProfile.Data.Length);
ExifProfile exifProfile = rootFrame.Metadata.ExifProfile;
TiffFrameMetadata tiffFrameMetadata = rootFrame.Metadata.GetTiffMetadata();
Assert.NotNull(exifProfile);
// The original exifProfile has 30 values, but 4 of those values will be stored in the TiffFrameMetaData
// and removed from the profile on decode.
Assert.Equal(26, exifProfile.Values.Count);
Assert.Equal(TiffBitsPerPixel.Bit4, tiffFrameMetadata.BitsPerPixel);
Assert.Equal(TiffCompression.Lzw, tiffFrameMetadata.Compression);
Assert.Equal("This is Название", exifProfile.GetValue(ExifTag.ImageDescription).Value);
Assert.Equal("This is Изготовитель камеры", exifProfile.GetValue(ExifTag.Make).Value);
Assert.Equal("This is Модель камеры", exifProfile.GetValue(ExifTag.Model).Value);
Assert.Equal("IrfanView", exifProfile.GetValue(ExifTag.Software).Value);
Assert.Null(exifProfile.GetValue(ExifTag.DateTime)?.Value);
Assert.Equal("This is author1;Author2", exifProfile.GetValue(ExifTag.Artist).Value);
Assert.Null(exifProfile.GetValue(ExifTag.HostComputer)?.Value);
Assert.Equal("This is Авторские права", exifProfile.GetValue(ExifTag.Copyright).Value);
Assert.Equal(4, exifProfile.GetValue(ExifTag.Rating).Value);
Assert.Equal(75, exifProfile.GetValue(ExifTag.RatingPercent).Value);
var expectedResolution = new Rational(10000, 1000, simplify: false);
Assert.Equal(expectedResolution, exifProfile.GetValue(ExifTag.XResolution).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[] { 297u }, exifProfile.GetValue(ExifTag.StripByteCounts)?.Value, new NumberComparer());
Assert.Null(exifProfile.GetValue(ExifTag.ExtraSamples)?.Value);
Assert.Equal(32u, exifProfile.GetValue(ExifTag.RowsPerStrip).Value);
Assert.Null(exifProfile.GetValue(ExifTag.SampleFormat));
Assert.Equal(TiffPredictor.None, tiffFrameMetadata.Predictor);
Assert.Equal(PixelResolutionUnit.PixelsPerInch, UnitConverter.ExifProfileToResolutionUnit(exifProfile));
ushort[] colorMap = exifProfile.GetValue(ExifTag.ColorMap)?.Value;
Assert.NotNull(colorMap);
Assert.Equal(48, colorMap.Length);
Assert.Equal(10537, colorMap[0]);
Assert.Equal(14392, colorMap[1]);
Assert.Equal(58596, colorMap[46]);
Assert.Equal(3855, colorMap[47]);
Assert.Equal(TiffPhotometricInterpretation.PaletteColor, tiffFrameMetadata.PhotometricInterpretation);
Assert.Equal(1u, exifProfile.GetValue(ExifTag.SamplesPerPixel).Value);
ImageMetadata imageMetaData = image.Metadata;
Assert.NotNull(imageMetaData);
Assert.Equal(PixelResolutionUnit.PixelsPerInch, imageMetaData.ResolutionUnits);
Assert.Equal(10, imageMetaData.HorizontalResolution);
Assert.Equal(10, imageMetaData.VerticalResolution);
TiffMetadata tiffMetaData = image.Metadata.GetTiffMetadata();
Assert.NotNull(tiffMetaData);
Assert.Equal(ByteOrder.LittleEndian, tiffMetaData.ByteOrder);
}
using Image<TPixel> image = provider.GetImage(TiffDecoder);
ImageFrame<TPixel> rootFrame = image.Frames.RootFrame;
Assert.Equal(32, rootFrame.Width);
Assert.Equal(32, rootFrame.Height);
Assert.NotNull(rootFrame.Metadata.XmpProfile);
Assert.Equal(2599, rootFrame.Metadata.XmpProfile.Data.Length);
ExifProfile exifProfile = rootFrame.Metadata.ExifProfile;
TiffFrameMetadata tiffFrameMetadata = rootFrame.Metadata.GetTiffMetadata();
Assert.NotNull(exifProfile);
// The original exifProfile has 30 values, but 4 of those values will be stored in the TiffFrameMetaData
// and removed from the profile on decode.
Assert.Equal(26, exifProfile.Values.Count);
Assert.Equal(TiffBitsPerPixel.Bit4, tiffFrameMetadata.BitsPerPixel);
Assert.Equal(TiffCompression.Lzw, tiffFrameMetadata.Compression);
Assert.Equal("This is Название", exifProfile.GetValue(ExifTag.ImageDescription).Value);
Assert.Equal("This is Изготовитель камеры", exifProfile.GetValue(ExifTag.Make).Value);
Assert.Equal("This is Модель камеры", exifProfile.GetValue(ExifTag.Model).Value);
Assert.Equal("IrfanView", exifProfile.GetValue(ExifTag.Software).Value);
Assert.Null(exifProfile.GetValue(ExifTag.DateTime)?.Value);
Assert.Equal("This is author1;Author2", exifProfile.GetValue(ExifTag.Artist).Value);
Assert.Null(exifProfile.GetValue(ExifTag.HostComputer)?.Value);
Assert.Equal("This is Авторские права", exifProfile.GetValue(ExifTag.Copyright).Value);
Assert.Equal(4, exifProfile.GetValue(ExifTag.Rating).Value);
Assert.Equal(75, exifProfile.GetValue(ExifTag.RatingPercent).Value);
var expectedResolution = new Rational(10000, 1000, simplify: false);
Assert.Equal(expectedResolution, exifProfile.GetValue(ExifTag.XResolution).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[] { 297u }, exifProfile.GetValue(ExifTag.StripByteCounts)?.Value, new NumberComparer());
Assert.Null(exifProfile.GetValue(ExifTag.ExtraSamples)?.Value);
Assert.Equal(32u, exifProfile.GetValue(ExifTag.RowsPerStrip).Value);
Assert.Null(exifProfile.GetValue(ExifTag.SampleFormat));
Assert.Equal(TiffPredictor.None, tiffFrameMetadata.Predictor);
Assert.Equal(PixelResolutionUnit.PixelsPerInch, UnitConverter.ExifProfileToResolutionUnit(exifProfile));
ushort[] colorMap = exifProfile.GetValue(ExifTag.ColorMap)?.Value;
Assert.NotNull(colorMap);
Assert.Equal(48, colorMap.Length);
Assert.Equal(10537, colorMap[0]);
Assert.Equal(14392, colorMap[1]);
Assert.Equal(58596, colorMap[46]);
Assert.Equal(3855, colorMap[47]);
Assert.Equal(TiffPhotometricInterpretation.PaletteColor, tiffFrameMetadata.PhotometricInterpretation);
Assert.Equal(1u, exifProfile.GetValue(ExifTag.SamplesPerPixel).Value);
ImageMetadata imageMetaData = image.Metadata;
Assert.NotNull(imageMetaData);
Assert.Equal(PixelResolutionUnit.PixelsPerInch, imageMetaData.ResolutionUnits);
Assert.Equal(10, imageMetaData.HorizontalResolution);
Assert.Equal(10, imageMetaData.VerticalResolution);
TiffMetadata tiffMetaData = image.Metadata.GetTiffMetadata();
Assert.NotNull(tiffMetaData);
Assert.Equal(ByteOrder.LittleEndian, tiffMetaData.ByteOrder);
}
[Theory]
@ -222,23 +219,21 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public void SubfileType<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(TiffDecoder))
{
TiffMetadata meta = image.Metadata.GetTiffMetadata();
Assert.NotNull(meta);
using Image<TPixel> image = provider.GetImage(TiffDecoder);
TiffMetadata meta = image.Metadata.GetTiffMetadata();
Assert.NotNull(meta);
Assert.Equal(2, image.Frames.Count);
Assert.Equal(2, image.Frames.Count);
ExifProfile frame0Exif = image.Frames[0].Metadata.ExifProfile;
Assert.Equal(TiffNewSubfileType.FullImage, (TiffNewSubfileType)frame0Exif.GetValue(ExifTag.SubfileType).Value);
Assert.Equal(255, image.Frames[0].Width);
Assert.Equal(255, image.Frames[0].Height);
ExifProfile frame0Exif = image.Frames[0].Metadata.ExifProfile;
Assert.Equal(TiffNewSubfileType.FullImage, (TiffNewSubfileType)frame0Exif.GetValue(ExifTag.SubfileType).Value);
Assert.Equal(255, image.Frames[0].Width);
Assert.Equal(255, image.Frames[0].Height);
ExifProfile frame1Exif = image.Frames[1].Metadata.ExifProfile;
Assert.Equal(TiffNewSubfileType.Preview, (TiffNewSubfileType)frame1Exif.GetValue(ExifTag.SubfileType).Value);
Assert.Equal(255, image.Frames[1].Width);
Assert.Equal(255, image.Frames[1].Height);
}
ExifProfile frame1Exif = image.Frames[1].Metadata.ExifProfile;
Assert.Equal(TiffNewSubfileType.Preview, (TiffNewSubfileType)frame1Exif.GetValue(ExifTag.SubfileType).Value);
Assert.Equal(255, image.Frames[1].Width);
Assert.Equal(255, image.Frames[1].Height);
}
[Theory]
@ -247,7 +242,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
where TPixel : unmanaged, IPixel<TPixel>
{
// Load Tiff image
using Image<TPixel> image = provider.GetImage(new TiffDecoder() { IgnoreMetadata = false });
DecoderOptions options = new() { SkipMetadata = false };
using Image<TPixel> image = provider.GetImage(new TiffDecoder(), options);
ImageMetadata inputMetaData = image.Metadata;
ImageFrame<TPixel> rootFrameInput = image.Frames.RootFrame;

4
tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs

@ -3,7 +3,6 @@
using System;
using System.IO;
using SixLabors.ImageSharp.Formats.Webp;
using SixLabors.ImageSharp.Formats.Webp.Lossless;
using SixLabors.ImageSharp.PixelFormats;
#if SUPPORTS_RUNTIME_INTRINSICS
@ -104,7 +103,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
Assert.Equal(expectedData, transformData);
}
// Test image: Input\Png\Bike.png
private static void RunColorSpaceTransformTestWithBikeImage()
{
// arrange
@ -119,7 +117,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
// Convert image pixels to bgra array.
byte[] imgBytes = File.ReadAllBytes(TestImageFullPath(TestImages.Webp.Lossy.BikeSmall));
using var image = Image.Load<Rgba32>(imgBytes, new WebpDecoder());
using var image = Image.Load<Rgba32>(imgBytes);
uint[] bgra = ToBgra(image);
int colorTransformBits = 4;

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

@ -1,9 +1,11 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using System.IO;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Formats.Webp;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
@ -44,14 +46,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
int expectedBitsPerPixel)
{
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
IImageInfo imageInfo = Image.Identify(stream);
Assert.NotNull(imageInfo);
Assert.Equal(expectedWidth, imageInfo.Width);
Assert.Equal(expectedHeight, imageInfo.Height);
Assert.Equal(expectedBitsPerPixel, imageInfo.PixelType.BitsPerPixel);
}
using var stream = new MemoryStream(testFile.Bytes, false);
IImageInfo imageInfo = Image.Identify(stream);
Assert.NotNull(imageInfo);
Assert.Equal(expectedWidth, imageInfo.Width);
Assert.Equal(expectedHeight, imageInfo.Height);
Assert.Equal(expectedBitsPerPixel, imageInfo.PixelType.BitsPerPixel);
}
[Theory]
@ -67,11 +67,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
public void WebpDecoder_CanDecode_Lossy_WithoutFilter<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(WebpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder);
}
using Image<TPixel> image = provider.GetImage(WebpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder);
}
[Theory]
@ -83,11 +81,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
public void WebpDecoder_CanDecode_Lossy_WithSimpleFilter<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(WebpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder);
}
using Image<TPixel> image = provider.GetImage(WebpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder);
}
[Theory]
@ -106,11 +102,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
public void WebpDecoder_CanDecode_Lossy_WithComplexFilter<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(WebpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder);
}
using Image<TPixel> image = provider.GetImage(WebpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder);
}
[Theory]
@ -121,11 +115,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
public void WebpDecoder_CanDecode_Lossy_VerySmall<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(WebpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder);
}
using Image<TPixel> image = provider.GetImage(WebpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder);
}
[Theory]
@ -140,11 +132,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
public void WebpDecoder_CanDecode_Lossy_WithPartitions<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(WebpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder);
}
using Image<TPixel> image = provider.GetImage(WebpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder);
}
[Theory]
@ -154,11 +144,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
public void WebpDecoder_CanDecode_Lossy_WithSegmentation<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(WebpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder);
}
using Image<TPixel> image = provider.GetImage(WebpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder);
}
[Theory]
@ -171,11 +159,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
public void WebpDecoder_CanDecode_Lossy_WithSharpnessLevel<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(WebpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder);
}
using Image<TPixel> image = provider.GetImage(WebpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder);
}
[Theory]
@ -196,11 +182,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
public void WebpDecoder_CanDecode_Lossy_WithAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(WebpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder);
}
using Image<TPixel> image = provider.GetImage(WebpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder);
}
[Theory]
@ -208,11 +192,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
public void WebpDecoder_CanDecode_Lossless_WithAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(WebpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder);
}
using Image<TPixel> image = provider.GetImage(WebpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder);
}
[Theory]
@ -221,11 +203,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
public void WebpDecoder_CanDecode_Lossless_WithoutTransforms<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(WebpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder);
}
using Image<TPixel> image = provider.GetImage(WebpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder);
}
[Theory]
@ -240,11 +220,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(WebpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder);
}
using Image<TPixel> image = provider.GetImage(WebpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder);
}
[Theory]
@ -256,11 +234,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
public void WebpDecoder_CanDecode_Lossless_WithColorIndexTransform<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(WebpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder);
}
using Image<TPixel> image = provider.GetImage(WebpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder);
}
[Theory]
@ -269,11 +245,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
public void WebpDecoder_CanDecode_Lossless_WithPredictorTransform<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(WebpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder);
}
using Image<TPixel> image = provider.GetImage(WebpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder);
}
[Theory]
@ -282,11 +256,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
public void WebpDecoder_CanDecode_Lossless_WithCrossColorTransform<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(WebpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder);
}
using Image<TPixel> image = provider.GetImage(WebpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder);
}
[Theory]
@ -306,11 +278,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
public void WebpDecoder_CanDecode_Lossless_WithTwoTransforms<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(WebpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder);
}
using Image<TPixel> image = provider.GetImage(WebpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder);
}
[Theory]
@ -325,11 +295,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
public void WebpDecoder_CanDecode_Lossless_WithThreeTransforms<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(WebpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder);
}
using Image<TPixel> image = provider.GetImage(WebpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder);
}
[Theory]
@ -337,18 +305,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
public void Decode_AnimatedLossless_VerifyAllFrames<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(WebpDecoder))
{
WebpMetadata webpMetaData = image.Metadata.GetWebpMetadata();
WebpFrameMetadata frameMetaData = image.Frames.RootFrame.Metadata.GetWebpMetadata();
using Image<TPixel> image = provider.GetImage(WebpDecoder);
WebpMetadata webpMetaData = image.Metadata.GetWebpMetadata();
WebpFrameMetadata frameMetaData = image.Frames.RootFrame.Metadata.GetWebpMetadata();
image.DebugSaveMultiFrame(provider);
image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact);
image.DebugSaveMultiFrame(provider);
image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact);
Assert.Equal(0, webpMetaData.AnimationLoopCount);
Assert.Equal(150U, frameMetaData.FrameDuration);
Assert.Equal(12, image.Frames.Count);
}
Assert.Equal(0, webpMetaData.AnimationLoopCount);
Assert.Equal(150U, frameMetaData.FrameDuration);
Assert.Equal(12, image.Frames.Count);
}
[Theory]
@ -356,18 +322,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
public void Decode_AnimatedLossy_VerifyAllFrames<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(WebpDecoder))
{
WebpMetadata webpMetaData = image.Metadata.GetWebpMetadata();
WebpFrameMetadata frameMetaData = image.Frames.RootFrame.Metadata.GetWebpMetadata();
using Image<TPixel> image = provider.GetImage(WebpDecoder);
WebpMetadata webpMetaData = image.Metadata.GetWebpMetadata();
WebpFrameMetadata frameMetaData = image.Frames.RootFrame.Metadata.GetWebpMetadata();
image.DebugSaveMultiFrame(provider);
image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Tolerant(0.04f));
image.DebugSaveMultiFrame(provider);
image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Tolerant(0.04f));
Assert.Equal(0, webpMetaData.AnimationLoopCount);
Assert.Equal(150U, frameMetaData.FrameDuration);
Assert.Equal(12, image.Frames.Count);
}
Assert.Equal(0, webpMetaData.AnimationLoopCount);
Assert.Equal(150U, frameMetaData.FrameDuration);
Assert.Equal(12, image.Frames.Count);
}
[Theory]
@ -375,10 +339,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
public void Decode_AnimatedLossless_WithFrameDecodingModeFirst_OnlyDecodesOneFrame<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new WebpDecoder() { DecodingMode = FrameDecodingMode.First }))
{
Assert.Equal(1, image.Frames.Count);
}
DecoderOptions options = new() { MaxFrames = 1 };
using Image<TPixel> image = provider.GetImage(new WebpDecoder(), options);
Assert.Equal(1, image.Frames.Count);
}
[Theory]
@ -389,10 +352,30 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
where TPixel : unmanaged, IPixel<TPixel>
{
// Just make sure no exception is thrown. The reference decoder fails to load the image.
using (Image<TPixel> image = provider.GetImage(WebpDecoder))
using Image<TPixel> image = provider.GetImage(WebpDecoder);
image.DebugSave(provider);
}
[Theory]
[WithFile(Lossless.BikeThreeTransforms, PixelTypes.Rgba32)]
public void WebpDecoder_Decode_Resize<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
DecoderOptions options = new()
{
image.DebugSave(provider);
}
TargetSize = new() { Width = 150, Height = 150 }
};
using Image<TPixel> image = provider.GetImage(WebpDecoder, options);
FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}";
image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false);
image.CompareToReferenceOutput(
ImageComparer.TolerantPercentage(0.0007F),
provider,
testOutputDetails: details,
appendPixelTypeToFileName: false);
}
// https://github.com/SixLabors/ImageSharp/issues/1594
@ -401,11 +384,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
public void WebpDecoder_CanDecode_Issue1594<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(WebpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder);
}
using Image<TPixel> image = provider.GetImage(WebpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder);
}
[Theory]
@ -424,41 +405,33 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
private static void RunDecodeLossyWithHorizontalFilter()
{
var provider = TestImageProvider<Rgba32>.File(TestImageLossyHorizontalFilterPath);
using (Image<Rgba32> image = provider.GetImage(WebpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder);
}
using Image<Rgba32> image = provider.GetImage(WebpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder);
}
private static void RunDecodeLossyWithVerticalFilter()
{
var provider = TestImageProvider<Rgba32>.File(TestImageLossyVerticalFilterPath);
using (Image<Rgba32> image = provider.GetImage(WebpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder);
}
using Image<Rgba32> image = provider.GetImage(WebpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder);
}
private static void RunDecodeLossyWithSimpleFilterTest()
{
var provider = TestImageProvider<Rgba32>.File(TestImageLossySimpleFilterPath);
using (Image<Rgba32> image = provider.GetImage(WebpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder);
}
using Image<Rgba32> image = provider.GetImage(WebpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder);
}
private static void RunDecodeLossyWithComplexFilterTest()
{
var provider = TestImageProvider<Rgba32>.File(TestImageLossyComplexFilterPath);
using (Image<Rgba32> image = provider.GetImage(WebpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder);
}
using Image<Rgba32> image = provider.GetImage(WebpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder);
}
[Fact]

61
tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs

@ -4,6 +4,7 @@
using System;
using System.IO;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Webp;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats;
@ -15,7 +16,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
[Trait("Format", "Webp")]
public class WebpMetaDataTests
{
private static WebpDecoder WebpDecoder => new() { IgnoreMetadata = false };
private static WebpDecoder WebpDecoder => new();
[Theory]
[WithFile(TestImages.Webp.Lossy.BikeWithExif, PixelTypes.Rgba32, false)]
@ -23,9 +24,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
public void IgnoreMetadata_ControlsWhetherExifIsParsed_WithLossyImage<TPixel>(TestImageProvider<TPixel> provider, bool ignoreMetadata)
where TPixel : unmanaged, IPixel<TPixel>
{
var decoder = new WebpDecoder { IgnoreMetadata = ignoreMetadata };
using Image<TPixel> image = provider.GetImage(decoder);
DecoderOptions options = new() { SkipMetadata = ignoreMetadata };
using Image<TPixel> image = provider.GetImage(WebpDecoder, options);
if (ignoreMetadata)
{
Assert.Null(image.Metadata.ExifProfile);
@ -45,9 +45,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
public void IgnoreMetadata_ControlsWhetherExifIsParsed_WithLosslessImage<TPixel>(TestImageProvider<TPixel> provider, bool ignoreMetadata)
where TPixel : unmanaged, IPixel<TPixel>
{
var decoder = new WebpDecoder { IgnoreMetadata = ignoreMetadata };
using Image<TPixel> image = provider.GetImage(decoder);
DecoderOptions options = new() { SkipMetadata = ignoreMetadata };
using Image<TPixel> image = provider.GetImage(WebpDecoder, options);
if (ignoreMetadata)
{
Assert.Null(image.Metadata.ExifProfile);
@ -71,9 +70,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
public void IgnoreMetadata_ControlsWhetherIccpIsParsed<TPixel>(TestImageProvider<TPixel> provider, bool ignoreMetadata)
where TPixel : unmanaged, IPixel<TPixel>
{
var decoder = new WebpDecoder { IgnoreMetadata = ignoreMetadata };
using Image<TPixel> image = provider.GetImage(decoder);
DecoderOptions options = new() { SkipMetadata = ignoreMetadata };
using Image<TPixel> image = provider.GetImage(WebpDecoder, options);
if (ignoreMetadata)
{
Assert.Null(image.Metadata.IccProfile);
@ -91,9 +89,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
public async Task IgnoreMetadata_ControlsWhetherXmpIsParsed<TPixel>(TestImageProvider<TPixel> provider, bool ignoreMetadata)
where TPixel : unmanaged, IPixel<TPixel>
{
var decoder = new WebpDecoder { IgnoreMetadata = ignoreMetadata };
using Image<TPixel> image = await provider.GetImageAsync(decoder);
DecoderOptions options = new() { SkipMetadata = ignoreMetadata };
using Image<TPixel> image = await provider.GetImageAsync(WebpDecoder, options);
if (ignoreMetadata)
{
Assert.Null(image.Metadata.XmpProfile);
@ -178,29 +175,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
public void Encode_PreservesColorProfile<TPixel>(TestImageProvider<TPixel> provider, WebpFileFormatType fileFormat)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> input = provider.GetImage(new WebpDecoder()))
using Image<TPixel> input = provider.GetImage(WebpDecoder);
ImageSharp.Metadata.Profiles.Icc.IccProfile expectedProfile = input.Metadata.IccProfile;
byte[] expectedProfileBytes = expectedProfile.ToByteArray();
using var memStream = new MemoryStream();
input.Save(memStream, new WebpEncoder()
{
ImageSharp.Metadata.Profiles.Icc.IccProfile expectedProfile = input.Metadata.IccProfile;
byte[] expectedProfileBytes = expectedProfile.ToByteArray();
using (var memStream = new MemoryStream())
{
input.Save(memStream, new WebpEncoder()
{
FileFormat = fileFormat
});
memStream.Position = 0;
using (var output = Image.Load<Rgba32>(memStream))
{
ImageSharp.Metadata.Profiles.Icc.IccProfile actualProfile = output.Metadata.IccProfile;
byte[] actualProfileBytes = actualProfile.ToByteArray();
Assert.NotNull(actualProfile);
Assert.Equal(expectedProfileBytes, actualProfileBytes);
}
}
}
FileFormat = fileFormat
});
memStream.Position = 0;
using var output = Image.Load<Rgba32>(memStream);
ImageSharp.Metadata.Profiles.Icc.IccProfile actualProfile = output.Metadata.IccProfile;
byte[] actualProfileBytes = actualProfile.ToByteArray();
Assert.NotNull(actualProfile);
Assert.Equal(expectedProfileBytes, actualProfileBytes);
}
[Theory]

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

@ -1,10 +1,10 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.PixelFormats;
using Xunit;
@ -15,101 +15,138 @@ namespace SixLabors.ImageSharp.Tests
public class Decode_Cancellation : ImageLoadTestBase
{
private bool isTestStreamSeekable;
private readonly SemaphoreSlim notifyWaitPositionReachedSemaphore = new SemaphoreSlim(0);
private readonly SemaphoreSlim continueSemaphore = new SemaphoreSlim(0);
private readonly CancellationTokenSource cts = new CancellationTokenSource();
private readonly SemaphoreSlim notifyWaitPositionReachedSemaphore = new(0);
private readonly SemaphoreSlim continueSemaphore = new(0);
private readonly CancellationTokenSource cts = new();
public Decode_Cancellation()
{
this.TopLevelConfiguration.StreamProcessingBufferSize = 128;
}
public Decode_Cancellation() => this.TopLevelConfiguration.StreamProcessingBufferSize = 128;
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task LoadAsync_Specific_Stream(bool isInputStreamSeekable)
public Task LoadAsync_Specific_Stream(bool isInputStreamSeekable)
{
this.isTestStreamSeekable = isInputStreamSeekable;
_ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning);
await Assert.ThrowsAsync<TaskCanceledException>(() => Image.LoadAsync<Rgb24>(this.TopLevelConfiguration, this.DataStream, this.cts.Token));
DecoderOptions options = new()
{
Configuration = this.TopLevelConfiguration
};
return Assert.ThrowsAsync<TaskCanceledException>(() => Image.LoadAsync<Rgb24>(options, this.DataStream, this.cts.Token));
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task LoadAsync_Agnostic_Stream(bool isInputStreamSeekable)
public Task LoadAsync_Agnostic_Stream(bool isInputStreamSeekable)
{
this.isTestStreamSeekable = isInputStreamSeekable;
_ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning);
await Assert.ThrowsAsync<TaskCanceledException>(() => Image.LoadAsync(this.TopLevelConfiguration, this.DataStream, this.cts.Token));
DecoderOptions options = new()
{
Configuration = this.TopLevelConfiguration
};
return Assert.ThrowsAsync<TaskCanceledException>(() => Image.LoadAsync(options, this.DataStream, this.cts.Token));
}
[Fact]
public async Task LoadAsync_Agnostic_Path()
public Task LoadAsync_Agnostic_Path()
{
this.isTestStreamSeekable = true;
_ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning);
await Assert.ThrowsAsync<TaskCanceledException>(() => Image.LoadAsync(this.TopLevelConfiguration, this.MockFilePath, this.cts.Token));
DecoderOptions options = new()
{
Configuration = this.TopLevelConfiguration
};
return Assert.ThrowsAsync<TaskCanceledException>(() => Image.LoadAsync(options, this.MockFilePath, this.cts.Token));
}
[Fact]
public async Task LoadAsync_Specific_Path()
public Task LoadAsync_Specific_Path()
{
this.isTestStreamSeekable = true;
_ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning);
await Assert.ThrowsAsync<TaskCanceledException>(() => Image.LoadAsync<Rgb24>(this.TopLevelConfiguration, this.MockFilePath, this.cts.Token));
DecoderOptions options = new()
{
Configuration = this.TopLevelConfiguration
};
return Assert.ThrowsAsync<TaskCanceledException>(() => Image.LoadAsync<Rgb24>(options, this.MockFilePath, this.cts.Token));
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task IdentifyAsync_Stream(bool isInputStreamSeekable)
public Task IdentifyAsync_Stream(bool isInputStreamSeekable)
{
this.isTestStreamSeekable = isInputStreamSeekable;
_ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning);
await Assert.ThrowsAsync<TaskCanceledException>(() => Image.IdentifyAsync(this.TopLevelConfiguration, this.DataStream, this.cts.Token));
DecoderOptions options = new()
{
Configuration = this.TopLevelConfiguration
};
return Assert.ThrowsAsync<TaskCanceledException>(() => Image.IdentifyAsync(options, this.DataStream, this.cts.Token));
}
[Fact]
public async Task IdentifyAsync_CustomConfiguration_Path()
public Task IdentifyAsync_CustomConfiguration_Path()
{
this.isTestStreamSeekable = true;
_ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning);
await Assert.ThrowsAsync<TaskCanceledException>(() => Image.IdentifyAsync(this.TopLevelConfiguration, this.MockFilePath, this.cts.Token));
DecoderOptions options = new()
{
Configuration = this.TopLevelConfiguration
};
return Assert.ThrowsAsync<TaskCanceledException>(() => Image.IdentifyAsync(options, this.MockFilePath, this.cts.Token));
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task IdentifyWithFormatAsync_CustomConfiguration_Stream(bool isInputStreamSeekable)
public Task IdentifyWithFormatAsync_CustomConfiguration_Stream(bool isInputStreamSeekable)
{
this.isTestStreamSeekable = isInputStreamSeekable;
_ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning);
await Assert.ThrowsAsync<TaskCanceledException>(() => Image.IdentifyWithFormatAsync(this.TopLevelConfiguration, this.DataStream, this.cts.Token));
DecoderOptions options = new()
{
Configuration = this.TopLevelConfiguration
};
return Assert.ThrowsAsync<TaskCanceledException>(() => Image.IdentifyWithFormatAsync(options, this.DataStream, this.cts.Token));
}
[Fact]
public async Task IdentifyWithFormatAsync_CustomConfiguration_Path()
public Task IdentifyWithFormatAsync_CustomConfiguration_Path()
{
this.isTestStreamSeekable = true;
_ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning);
await Assert.ThrowsAsync<TaskCanceledException>(() => Image.IdentifyWithFormatAsync(this.TopLevelConfiguration, this.MockFilePath, this.cts.Token));
DecoderOptions options = new()
{
Configuration = this.TopLevelConfiguration
};
return Assert.ThrowsAsync<TaskCanceledException>(() => Image.IdentifyWithFormatAsync(options, this.MockFilePath, this.cts.Token));
}
[Fact]
public async Task IdentifyWithFormatAsync_DefaultConfiguration_Stream()
public Task IdentifyWithFormatAsync_DefaultConfiguration_Stream()
{
_ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning);
await Assert.ThrowsAsync<TaskCanceledException>(() => Image.IdentifyWithFormatAsync(this.DataStream, this.cts.Token));
return Assert.ThrowsAsync<TaskCanceledException>(() => Image.IdentifyWithFormatAsync(this.DataStream, this.cts.Token));
}
private async Task DoCancel()

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

@ -29,26 +29,23 @@ namespace SixLabors.ImageSharp.Tests
private static readonly IImageFormat ExpectedGlobalFormat =
Configuration.Default.ImageFormatsManager.FindFormatByFileExtension("bmp");
[Theory]
[InlineData(false)]
[InlineData(true)]
public void FromBytes_GlobalConfiguration(bool useSpan)
[Fact]
public void FromBytes_GlobalConfiguration()
{
IImageFormat type = useSpan
? Image.DetectFormat(this.ActualImageSpan)
: Image.DetectFormat(this.ActualImageBytes);
IImageFormat type = Image.DetectFormat(this.ActualImageSpan);
Assert.Equal(ExpectedGlobalFormat, type);
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void FromBytes_CustomConfiguration(bool useSpan)
[Fact]
public void FromBytes_CustomConfiguration()
{
IImageFormat type = useSpan
? Image.DetectFormat(this.LocalConfiguration, this.ByteArray.AsSpan())
: Image.DetectFormat(this.LocalConfiguration, this.ByteArray);
DecoderOptions options = new()
{
Configuration = this.LocalConfiguration
};
IImageFormat type = Image.DetectFormat(options, this.ByteArray);
Assert.Equal(this.LocalImageFormat, type);
}
@ -63,7 +60,12 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void FromFileSystemPath_CustomConfiguration()
{
IImageFormat type = Image.DetectFormat(this.LocalConfiguration, this.MockFilePath);
DecoderOptions options = new()
{
Configuration = this.LocalConfiguration
};
IImageFormat type = Image.DetectFormat(options, this.MockFilePath);
Assert.Equal(this.LocalImageFormat, type);
}
@ -80,14 +82,24 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void FromStream_CustomConfiguration()
{
IImageFormat type = Image.DetectFormat(this.LocalConfiguration, this.DataStream);
DecoderOptions options = new()
{
Configuration = this.LocalConfiguration
};
IImageFormat type = Image.DetectFormat(options, this.DataStream);
Assert.Equal(this.LocalImageFormat, type);
}
[Fact]
public void WhenNoMatchingFormatFound_ReturnsNull()
{
IImageFormat type = Image.DetectFormat(new Configuration(), this.DataStream);
DecoderOptions options = new()
{
Configuration = new()
};
IImageFormat type = Image.DetectFormat(options, this.DataStream);
Assert.Null(type);
}
@ -104,14 +116,24 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public async Task FromStreamAsync_CustomConfiguration()
{
IImageFormat type = await Image.DetectFormatAsync(this.LocalConfiguration, new AsyncStreamWrapper(this.DataStream, () => false));
DecoderOptions options = new()
{
Configuration = this.LocalConfiguration
};
IImageFormat type = await Image.DetectFormatAsync(options, new AsyncStreamWrapper(this.DataStream, () => false));
Assert.Equal(this.LocalImageFormat, type);
}
[Fact]
public async Task WhenNoMatchingFormatFoundAsync_ReturnsNull()
{
IImageFormat type = await Image.DetectFormatAsync(new Configuration(), new AsyncStreamWrapper(this.DataStream, () => false));
DecoderOptions options = new()
{
Configuration = new()
};
IImageFormat type = await Image.DetectFormatAsync(options, new AsyncStreamWrapper(this.DataStream, () => false));
Assert.Null(type);
}
}

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

@ -20,7 +20,7 @@ namespace SixLabors.ImageSharp.Tests
{
private static readonly string ActualImagePath = TestFile.GetInputFileFullPath(TestImages.Bmp.F);
private static readonly Size ExpectedImageSize = new Size(108, 202);
private static readonly Size ExpectedImageSize = new(108, 202);
private static byte[] ActualImageBytes => TestFile.Create(TestImages.Bmp.F).Bytes;
@ -43,7 +43,12 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void FromBytes_CustomConfiguration()
{
IImageInfo info = Image.Identify(this.LocalConfiguration, this.ByteArray, out IImageFormat type);
DecoderOptions options = new()
{
Configuration = this.LocalConfiguration
};
IImageInfo info = Image.Identify(options, this.ByteArray, out IImageFormat type);
Assert.Equal(this.LocalImageInfo, info);
Assert.Equal(this.LocalImageFormat, type);
@ -61,7 +66,12 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void FromFileSystemPath_CustomConfiguration()
{
IImageInfo info = Image.Identify(this.LocalConfiguration, this.MockFilePath, out IImageFormat type);
DecoderOptions options = new()
{
Configuration = this.LocalConfiguration
};
IImageInfo info = Image.Identify(options, this.MockFilePath, out IImageFormat type);
Assert.Equal(this.LocalImageInfo, info);
Assert.Equal(this.LocalImageFormat, type);
@ -70,24 +80,20 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void FromStream_GlobalConfiguration()
{
using (var stream = new MemoryStream(ActualImageBytes))
{
IImageInfo info = Image.Identify(stream, out IImageFormat type);
using var stream = new MemoryStream(ActualImageBytes);
IImageInfo info = Image.Identify(stream, out IImageFormat type);
Assert.NotNull(info);
Assert.Equal(ExpectedGlobalFormat, type);
}
Assert.NotNull(info);
Assert.Equal(ExpectedGlobalFormat, type);
}
[Fact]
public void FromStream_GlobalConfiguration_NoFormat()
{
using (var stream = new MemoryStream(ActualImageBytes))
{
IImageInfo info = Image.Identify(stream);
using var stream = new MemoryStream(ActualImageBytes);
IImageInfo info = Image.Identify(stream);
Assert.NotNull(info);
}
Assert.NotNull(info);
}
[Fact]
@ -116,7 +122,12 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void FromStream_CustomConfiguration()
{
IImageInfo info = Image.Identify(this.LocalConfiguration, this.DataStream, out IImageFormat type);
DecoderOptions options = new()
{
Configuration = this.LocalConfiguration
};
IImageInfo info = Image.Identify(options, this.DataStream, out IImageFormat type);
Assert.Equal(this.LocalImageInfo, info);
Assert.Equal(this.LocalImageFormat, type);
@ -125,7 +136,12 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void FromStream_CustomConfiguration_NoFormat()
{
IImageInfo info = Image.Identify(this.LocalConfiguration, this.DataStream);
DecoderOptions options = new()
{
Configuration = this.LocalConfiguration
};
IImageInfo info = Image.Identify(options, this.DataStream);
Assert.Equal(this.LocalImageInfo, info);
}
@ -133,7 +149,12 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void WhenNoMatchingFormatFound_ReturnsNull()
{
IImageInfo info = Image.Identify(new Configuration(), this.DataStream, out IImageFormat type);
DecoderOptions options = new()
{
Configuration = new()
};
IImageInfo info = Image.Identify(options, this.DataStream, out IImageFormat type);
Assert.Null(info);
Assert.Null(type);
@ -168,26 +189,22 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public async Task FromStreamAsync_GlobalConfiguration_NoFormat()
{
using (var stream = new MemoryStream(ActualImageBytes))
{
var asyncStream = new AsyncStreamWrapper(stream, () => false);
IImageInfo info = await Image.IdentifyAsync(asyncStream);
using var stream = new MemoryStream(ActualImageBytes);
var asyncStream = new AsyncStreamWrapper(stream, () => false);
IImageInfo info = await Image.IdentifyAsync(asyncStream);
Assert.NotNull(info);
}
Assert.NotNull(info);
}
[Fact]
public async Task FromStreamAsync_GlobalConfiguration()
{
using (var stream = new MemoryStream(ActualImageBytes))
{
var asyncStream = new AsyncStreamWrapper(stream, () => false);
(IImageInfo ImageInfo, IImageFormat Format) res = await Image.IdentifyWithFormatAsync(asyncStream);
using var stream = new MemoryStream(ActualImageBytes);
var asyncStream = new AsyncStreamWrapper(stream, () => false);
(IImageInfo ImageInfo, IImageFormat Format) res = await Image.IdentifyWithFormatAsync(asyncStream);
Assert.Equal(ExpectedImageSize, res.ImageInfo.Size());
Assert.Equal(ExpectedGlobalFormat, res.Format);
}
Assert.Equal(ExpectedImageSize, res.ImageInfo.Size());
Assert.Equal(ExpectedGlobalFormat, res.Format);
}
[Fact]
@ -244,14 +261,24 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public async Task FromPathAsync_CustomConfiguration()
{
IImageInfo info = await Image.IdentifyAsync(this.LocalConfiguration, this.MockFilePath);
DecoderOptions options = new()
{
Configuration = this.LocalConfiguration
};
IImageInfo info = await Image.IdentifyAsync(options, this.MockFilePath);
Assert.Equal(this.LocalImageInfo, info);
}
[Fact]
public async Task IdentifyWithFormatAsync_FromPath_CustomConfiguration()
{
(IImageInfo ImageInfo, IImageFormat Format) info = await Image.IdentifyWithFormatAsync(this.LocalConfiguration, this.MockFilePath);
DecoderOptions options = new()
{
Configuration = this.LocalConfiguration
};
(IImageInfo ImageInfo, IImageFormat Format) info = await Image.IdentifyWithFormatAsync(options, this.MockFilePath);
Assert.NotNull(info.ImageInfo);
Assert.Equal(this.LocalImageFormat, info.Format);
}
@ -276,8 +303,13 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public async Task FromStreamAsync_CustomConfiguration()
{
DecoderOptions options = new()
{
Configuration = this.LocalConfiguration
};
var asyncStream = new AsyncStreamWrapper(this.DataStream, () => false);
(IImageInfo ImageInfo, IImageFormat Format) info = await Image.IdentifyWithFormatAsync(this.LocalConfiguration, asyncStream);
(IImageInfo ImageInfo, IImageFormat Format) info = await Image.IdentifyWithFormatAsync(options, asyncStream);
Assert.Equal(this.LocalImageInfo, info.ImageInfo);
Assert.Equal(this.LocalImageFormat, info.Format);
@ -286,8 +318,13 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public async Task WhenNoMatchingFormatFoundAsync_ReturnsNull()
{
DecoderOptions options = new()
{
Configuration = new()
};
var asyncStream = new AsyncStreamWrapper(this.DataStream, () => false);
(IImageInfo ImageInfo, IImageFormat Format) info = await Image.IdentifyWithFormatAsync(new Configuration(), asyncStream);
(IImageInfo ImageInfo, IImageFormat Format) info = await Image.IdentifyWithFormatAsync(options, asyncStream);
Assert.Null(info.ImageInfo);
}

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

@ -63,28 +63,27 @@ namespace SixLabors.ImageSharp.Tests
this.localImageFormatMock = new Mock<IImageFormat>();
var detector = new Mock<IImageInfoDetector>();
detector.Setup(x => x.Identify(It.IsAny<Configuration>(), It.IsAny<Stream>(), It.IsAny<CancellationToken>())).Returns(this.localImageInfoMock.Object);
detector.Setup(x => x.Identify(It.IsAny<DecoderOptions>(), It.IsAny<Stream>(), It.IsAny<CancellationToken>()))
.Returns(this.localImageInfoMock.Object);
this.localDecoder = detector.As<IImageDecoder>();
this.localDecoder.Setup(x => x.Decode<Rgba32>(It.IsAny<Configuration>(), It.IsAny<Stream>(), It.IsAny<CancellationToken>()))
.Callback<Configuration, Stream, CancellationToken>((c, s, ct) =>
this.localDecoder
.Setup(x => x.Decode<Rgba32>(It.IsAny<DecoderOptions>(), It.IsAny<Stream>(), It.IsAny<CancellationToken>()))
.Callback<DecoderOptions, Stream, CancellationToken>((c, s, ct) =>
{
using (var ms = new MemoryStream())
{
s.CopyTo(ms);
this.DecodedData = ms.ToArray();
}
using var ms = new MemoryStream();
s.CopyTo(ms);
this.DecodedData = ms.ToArray();
})
.Returns(this.localStreamReturnImageRgba32);
this.localDecoder.Setup(x => x.Decode(It.IsAny<Configuration>(), It.IsAny<Stream>(), It.IsAny<CancellationToken>()))
.Callback<Configuration, Stream, CancellationToken>((c, s, ct) =>
this.localDecoder
.Setup(x => x.Decode(It.IsAny<DecoderOptions>(), It.IsAny<Stream>(), It.IsAny<CancellationToken>()))
.Callback<DecoderOptions, Stream, CancellationToken>((c, s, ct) =>
{
using (var ms = new MemoryStream())
{
s.CopyTo(ms);
this.DecodedData = ms.ToArray();
}
using var ms = new MemoryStream();
s.CopyTo(ms);
this.DecodedData = ms.ToArray();
})
.Returns(this.localStreamReturnImageAgnostic);

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

@ -16,7 +16,12 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void Configuration_Path_Specific()
{
var img = Image.Load<Rgb24>(this.TopLevelConfiguration, this.MockFilePath);
DecoderOptions options = new()
{
Configuration = this.TopLevelConfiguration
};
var img = Image.Load<Rgb24>(options, this.MockFilePath);
Assert.NotNull(img);
Assert.Equal(this.TestFormat.Sample<Rgb24>(), img);
@ -27,7 +32,12 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void Configuration_Path_Agnostic()
{
var img = Image.Load(this.TopLevelConfiguration, this.MockFilePath);
DecoderOptions options = new()
{
Configuration = this.TopLevelConfiguration
};
var img = Image.Load(options, this.MockFilePath);
Assert.NotNull(img);
Assert.Equal(this.TestFormat.SampleAgnostic(), img);
@ -35,28 +45,15 @@ namespace SixLabors.ImageSharp.Tests
this.TestFormat.VerifyAgnosticDecodeCall(this.Marker, this.TopLevelConfiguration);
}
[Fact]
public void Configuration_Path_Decoder_Specific()
{
var img = Image.Load<Rgba32>(this.TopLevelConfiguration, this.MockFilePath, this.localDecoder.Object);
Assert.NotNull(img);
this.localDecoder.Verify(x => x.Decode<Rgba32>(this.TopLevelConfiguration, this.DataStream, default));
}
[Fact]
public void Configuration_Path_Decoder_Agnostic()
{
var img = Image.Load(this.TopLevelConfiguration, this.MockFilePath, this.localDecoder.Object);
Assert.NotNull(img);
this.localDecoder.Verify(x => x.Decode(this.TopLevelConfiguration, this.DataStream, default));
}
[Fact]
public void Configuration_Path_OutFormat_Specific()
{
var img = Image.Load<Rgba32>(this.TopLevelConfiguration, this.MockFilePath, out IImageFormat format);
DecoderOptions options = new()
{
Configuration = this.TopLevelConfiguration
};
var img = Image.Load<Rgba32>(options, this.MockFilePath, out IImageFormat format);
Assert.NotNull(img);
Assert.Equal(this.TestFormat, format);
@ -67,7 +64,12 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void Configuration_Path_OutFormat_Agnostic()
{
var img = Image.Load(this.TopLevelConfiguration, this.MockFilePath, out IImageFormat format);
DecoderOptions options = new()
{
Configuration = this.TopLevelConfiguration
};
var img = Image.Load(options, this.MockFilePath, out IImageFormat format);
Assert.NotNull(img);
Assert.Equal(this.TestFormat, format);
@ -77,23 +79,28 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void WhenFileNotFound_Throws()
{
Assert.Throws<System.IO.FileNotFoundException>(
=> Assert.Throws<System.IO.FileNotFoundException>(
() =>
{
Image.Load<Rgba32>(this.TopLevelConfiguration, Guid.NewGuid().ToString());
DecoderOptions options = new()
{
Configuration = this.TopLevelConfiguration
};
Image.Load<Rgba32>(options, Guid.NewGuid().ToString());
});
}
[Fact]
public void WhenPathIsNull_Throws()
{
Assert.Throws<ArgumentNullException>(
=> Assert.Throws<ArgumentNullException>(
() =>
{
Image.Load<Rgba32>(this.TopLevelConfiguration, (string)null);
DecoderOptions options = new()
{
Configuration = this.TopLevelConfiguration
};
Image.Load<Rgba32>(options, (string)null);
});
}
}
}
}

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

@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License.
using System;
using System.IO;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Bmp;
@ -17,10 +18,7 @@ namespace SixLabors.ImageSharp.Tests
{
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]
public void Path_Specific()
@ -39,49 +37,21 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public async Task Path_Agnostic_Async()
{
using var img = await Image.LoadAsync(this.Path);
using Image img = await Image.LoadAsync(this.Path);
VerifyDecodedImage(img);
}
[Fact]
public async Task Path_Specific_Async()
{
using var img = await Image.LoadAsync<Rgb24>(this.Path);
using Image<Rgb24> img = await Image.LoadAsync<Rgb24>(this.Path);
VerifyDecodedImage(img);
}
[Fact]
public async Task Path_Agnostic_Configuration_Async()
{
using var img = await Image.LoadAsync(this.Path);
VerifyDecodedImage(img);
}
[Fact]
public void Path_Decoder_Specific()
{
using var img = Image.Load<Rgba32>(this.Path, new BmpDecoder());
VerifyDecodedImage(img);
}
[Fact]
public void Path_Decoder_Agnostic()
{
using var img = Image.Load(this.Path, new BmpDecoder());
VerifyDecodedImage(img);
}
[Fact]
public async Task Path_Decoder_Agnostic_Async()
{
using var img = await Image.LoadAsync(this.Path, new BmpDecoder());
VerifyDecodedImage(img);
}
[Fact]
public async Task Path_Decoder_Specific_Async()
{
using var img = await Image.LoadAsync<Rgb24>(this.Path, new BmpDecoder());
using Image img = await Image.LoadAsync(this.Path);
VerifyDecodedImage(img);
}
@ -103,37 +73,19 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void WhenFileNotFound_Throws()
{
Assert.Throws<System.IO.FileNotFoundException>(
() =>
{
Image.Load<Rgba32>(Guid.NewGuid().ToString());
});
}
=> Assert.Throws<FileNotFoundException>(() => Image.Load<Rgba32>(Guid.NewGuid().ToString()));
[Fact]
public void WhenPathIsNull_Throws()
{
Assert.Throws<ArgumentNullException>(
() =>
{
Image.Load<Rgba32>((string)null);
});
}
=> Assert.Throws<ArgumentNullException>(() => Image.Load<Rgba32>((string)null));
[Fact]
public Task Async_WhenFileNotFound_Throws()
{
return Assert.ThrowsAsync<System.IO.FileNotFoundException>(
() => Image.LoadAsync<Rgba32>(Guid.NewGuid().ToString()));
}
=> Assert.ThrowsAsync<FileNotFoundException>(() => Image.LoadAsync<Rgba32>(Guid.NewGuid().ToString()));
[Fact]
public Task Async_WhenPathIsNull_Throws()
{
return Assert.ThrowsAsync<ArgumentNullException>(
() => Image.LoadAsync<Rgba32>((string)null));
}
=> Assert.ThrowsAsync<ArgumentNullException>(() => Image.LoadAsync<Rgba32>((string)null));
}
}
}

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

@ -15,14 +15,15 @@ namespace SixLabors.ImageSharp.Tests
{
private ReadOnlySpan<byte> ByteSpan => this.ByteArray.AsSpan();
[Theory]
[InlineData(false)]
[InlineData(true)]
public void Configuration_Bytes_Specific(bool useSpan)
[Fact]
public void Configuration_Bytes_Specific()
{
var img = useSpan
? Image.Load<Rgb24>(this.TopLevelConfiguration, this.ByteSpan)
: Image.Load<Rgb24>(this.TopLevelConfiguration, this.ByteArray);
DecoderOptions options = new()
{
Configuration = this.TopLevelConfiguration
};
var img = Image.Load<Rgb24>(options, this.ByteSpan);
Assert.NotNull(img);
Assert.Equal(this.TestFormat.Sample<Rgb24>(), img);
@ -30,14 +31,15 @@ namespace SixLabors.ImageSharp.Tests
this.TestFormat.VerifySpecificDecodeCall<Rgb24>(this.Marker, this.TopLevelConfiguration);
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void Configuration_Bytes_Agnostic(bool useSpan)
[Fact]
public void Configuration_Bytes_Agnostic()
{
var img = useSpan
? Image.Load(this.TopLevelConfiguration, this.ByteSpan)
: Image.Load(this.TopLevelConfiguration, this.ByteArray);
DecoderOptions options = new()
{
Configuration = this.TopLevelConfiguration
};
var img = Image.Load(options, this.ByteSpan);
Assert.NotNull(img);
Assert.Equal(this.TestFormat.SampleAgnostic(), img);
@ -45,45 +47,15 @@ namespace SixLabors.ImageSharp.Tests
this.TestFormat.VerifyAgnosticDecodeCall(this.Marker, this.TopLevelConfiguration);
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void Configuration_Bytes_Decoder_Specific(bool useSpan)
{
var localFormat = new TestFormat();
var img = useSpan ?
Image.Load<Rgba32>(this.TopLevelConfiguration, this.ByteSpan, localFormat.Decoder) :
Image.Load<Rgba32>(this.TopLevelConfiguration, this.ByteArray, localFormat.Decoder);
Assert.NotNull(img);
localFormat.VerifySpecificDecodeCall<Rgba32>(this.Marker, this.TopLevelConfiguration);
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void Configuration_Bytes_Decoder_Agnostic(bool useSpan)
[Fact]
public void Configuration_Bytes_OutFormat_Specific()
{
var localFormat = new TestFormat();
DecoderOptions options = new()
{
Configuration = this.TopLevelConfiguration
};
var img = useSpan ?
Image.Load(this.TopLevelConfiguration, this.ByteSpan, localFormat.Decoder) :
Image.Load(this.TopLevelConfiguration, this.ByteArray, localFormat.Decoder);
Assert.NotNull(img);
localFormat.VerifyAgnosticDecodeCall(this.Marker, this.TopLevelConfiguration);
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void Configuration_Bytes_OutFormat_Specific(bool useSpan)
{
IImageFormat format;
var img = useSpan ?
Image.Load<Bgr24>(this.TopLevelConfiguration, this.ByteSpan, out format) :
Image.Load<Bgr24>(this.TopLevelConfiguration, this.ByteArray, out format);
var img = Image.Load<Bgr24>(options, this.ByteSpan, out IImageFormat format);
Assert.NotNull(img);
Assert.Equal(this.TestFormat, format);
@ -91,15 +63,15 @@ namespace SixLabors.ImageSharp.Tests
this.TestFormat.VerifySpecificDecodeCall<Bgr24>(this.Marker, this.TopLevelConfiguration);
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void Configuration_Bytes_OutFormat_Agnostic(bool useSpan)
[Fact]
public void Configuration_Bytes_OutFormat_Agnostic()
{
IImageFormat format;
var img = useSpan ?
Image.Load(this.TopLevelConfiguration, this.ByteSpan, out format) :
Image.Load(this.TopLevelConfiguration, this.ByteArray, out format);
DecoderOptions options = new()
{
Configuration = this.TopLevelConfiguration
};
var img = Image.Load(options, this.ByteSpan, out IImageFormat format);
Assert.NotNull(img);
Assert.Equal(this.TestFormat, format);

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

@ -16,81 +16,38 @@ namespace SixLabors.ImageSharp.Tests
{
private static byte[] ByteArray { get; } = TestFile.Create(TestImages.Bmp.Bit8).Bytes;
private static Span<byte> ByteSpan => new Span<byte>(ByteArray);
private static Span<byte> ByteSpan => new(ByteArray);
private static void VerifyDecodedImage(Image img)
{
Assert.Equal(new Size(127, 64), img.Size());
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void Bytes_Specific(bool useSpan)
{
using (var img = useSpan ? Image.Load<Rgba32>(ByteSpan) : Image.Load<Rgba32>(ByteArray))
{
VerifyDecodedImage(img);
}
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void Bytes_Agnostic(bool useSpan)
{
using (var img = useSpan ? Image.Load(ByteSpan) : Image.Load(ByteArray))
{
VerifyDecodedImage(img);
}
}
private static void VerifyDecodedImage(Image img) => Assert.Equal(new Size(127, 64), img.Size());
[Theory]
[InlineData(false)]
[InlineData(true)]
public void Bytes_Decoder_Specific(bool useSpan)
[Fact]
public void Bytes_Specific()
{
using (var img = useSpan ? Image.Load<Rgba32>(ByteSpan, new BmpDecoder()) : Image.Load<Rgba32>(ByteArray, new BmpDecoder()))
{
VerifyDecodedImage(img);
}
using var img = Image.Load<Rgba32>(ByteSpan);
VerifyDecodedImage(img);
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void Bytes_Decoder_Agnostic(bool useSpan)
[Fact]
public void Bytes_Agnostic()
{
using (var img = useSpan ? Image.Load(ByteSpan, new BmpDecoder()) : Image.Load(ByteArray, new BmpDecoder()))
{
VerifyDecodedImage(img);
}
using var img = Image.Load(ByteSpan);
VerifyDecodedImage(img);
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void Bytes_OutFormat_Specific(bool useSpan)
[Fact]
public void Bytes_OutFormat_Specific()
{
IImageFormat format;
using (var img = useSpan ? Image.Load<Rgba32>(ByteSpan, out format) : Image.Load<Rgba32>(ByteArray, out format))
{
VerifyDecodedImage(img);
Assert.IsType<BmpFormat>(format);
}
using var img = Image.Load<Rgba32>(ByteSpan, out IImageFormat format);
VerifyDecodedImage(img);
Assert.IsType<BmpFormat>(format);
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void Bytes_OutFormat_Agnostic(bool useSpan)
[Fact]
public void Bytes_OutFormat_Agnostic()
{
IImageFormat format;
using (var img = useSpan ? Image.Load(ByteSpan, out format) : Image.Load(ByteArray, out format))
{
VerifyDecodedImage(img);
Assert.IsType<BmpFormat>(format);
}
using var img = Image.Load(ByteSpan, out IImageFormat format);
VerifyDecodedImage(img);
Assert.IsType<BmpFormat>(format);
}
}
}

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

@ -1,7 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.IO;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.PixelFormats;
@ -17,7 +16,12 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void Configuration_Stream_Specific()
{
var img = Image.Load<Rgb24>(this.TopLevelConfiguration, this.DataStream);
DecoderOptions options = new()
{
Configuration = this.TopLevelConfiguration
};
var img = Image.Load<Rgb24>(options, this.DataStream);
Assert.NotNull(img);
Assert.Equal(this.TestFormat.Sample<Rgb24>(), img);
@ -28,7 +32,12 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void Configuration_Stream_Agnostic()
{
var img = Image.Load(this.TopLevelConfiguration, this.DataStream);
DecoderOptions options = new()
{
Configuration = this.TopLevelConfiguration
};
var img = Image.Load(options, this.DataStream);
Assert.NotNull(img);
Assert.Equal(this.TestFormat.SampleAgnostic(), img);
@ -39,8 +48,13 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void NonSeekableStream()
{
DecoderOptions options = new()
{
Configuration = this.TopLevelConfiguration
};
var stream = new NonSeekableStream(this.DataStream);
var img = Image.Load<Rgba32>(this.TopLevelConfiguration, stream);
var img = Image.Load<Rgba32>(options, stream);
Assert.NotNull(img);
@ -50,38 +64,28 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public async Task NonSeekableStreamAsync()
{
DecoderOptions options = new()
{
Configuration = this.TopLevelConfiguration
};
var stream = new NonSeekableStream(this.DataStream);
Image<Rgba32> img = await Image.LoadAsync<Rgba32>(this.TopLevelConfiguration, stream);
Image<Rgba32> img = await Image.LoadAsync<Rgba32>(options, stream);
Assert.NotNull(img);
this.TestFormat.VerifySpecificDecodeCall<Rgba32>(this.Marker, this.TopLevelConfiguration);
}
[Fact]
public void Configuration_Stream_Decoder_Specific()
{
var stream = new MemoryStream();
var img = Image.Load<Rgba32>(this.TopLevelConfiguration, stream, this.localDecoder.Object);
Assert.NotNull(img);
this.localDecoder.Verify(x => x.Decode<Rgba32>(this.TopLevelConfiguration, stream, default));
}
[Fact]
public void Configuration_Stream_Decoder_Agnostic()
{
var stream = new MemoryStream();
var img = Image.Load(this.TopLevelConfiguration, stream, this.localDecoder.Object);
Assert.NotNull(img);
this.localDecoder.Verify(x => x.Decode(this.TopLevelConfiguration, stream, default));
}
[Fact]
public void Configuration_Stream_OutFormat_Specific()
{
var img = Image.Load<Rgba32>(this.TopLevelConfiguration, this.DataStream, out IImageFormat format);
DecoderOptions options = new()
{
Configuration = this.TopLevelConfiguration
};
var img = Image.Load<Rgba32>(options, this.DataStream, out IImageFormat format);
Assert.NotNull(img);
Assert.Equal(this.TestFormat, format);
@ -92,7 +96,12 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void Configuration_Stream_OutFormat_Agnostic()
{
var img = Image.Load(this.TopLevelConfiguration, this.DataStream, out IImageFormat format);
DecoderOptions options = new()
{
Configuration = this.TopLevelConfiguration
};
var img = Image.Load(options, this.DataStream, out IImageFormat format);
Assert.NotNull(img);
Assert.Equal(this.TestFormat, format);

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

@ -20,30 +20,23 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void Image_Load_Throws_UnknownImageFormatException()
{
Assert.Throws<UnknownImageFormatException>(() =>
=> Assert.Throws<UnknownImageFormatException>(() =>
{
using (Image.Load(Configuration.Default, this.Stream, out IImageFormat format))
using (Image.Load(DecoderOptions.Default, this.Stream, out IImageFormat format))
{
}
});
}
[Fact]
public void Image_Load_T_Throws_UnknownImageFormatException()
{
Assert.Throws<UnknownImageFormatException>(() =>
=> Assert.Throws<UnknownImageFormatException>(() =>
{
using (Image.Load<Rgba32>(Configuration.Default, this.Stream, out IImageFormat format))
using (Image.Load<Rgba32>(DecoderOptions.Default, this.Stream, out IImageFormat format))
{
}
});
}
public void Dispose()
{
this.Stream?.Dispose();
}
public void Dispose() => this.Stream?.Dispose();
}
}
}

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

@ -31,71 +31,43 @@ namespace SixLabors.ImageSharp.Tests
}
private static void VerifyDecodedImage(Image img)
{
Assert.Equal(new Size(127, 64), img.Size());
}
=> Assert.Equal(new Size(127, 64), img.Size());
[Fact]
public void Stream_Specific()
{
using (var img = Image.Load<Rgba32>(this.Stream))
{
VerifyDecodedImage(img);
}
using var img = Image.Load<Rgba32>(this.Stream);
VerifyDecodedImage(img);
}
[Fact]
public void Stream_Agnostic()
{
using (var img = Image.Load(this.Stream))
{
VerifyDecodedImage(img);
}
using var img = Image.Load(this.Stream);
VerifyDecodedImage(img);
}
[Fact]
public void Stream_OutFormat_Specific()
{
using (var img = Image.Load<Rgba32>(this.Stream, out IImageFormat format))
{
VerifyDecodedImage(img);
Assert.IsType<BmpFormat>(format);
}
}
[Fact]
public void Stream_Decoder_Specific()
{
using (var img = Image.Load<Rgba32>(this.Stream, new BmpDecoder()))
{
VerifyDecodedImage(img);
}
}
[Fact]
public void Stream_Decoder_Agnostic()
{
using (var img = Image.Load(this.Stream, new BmpDecoder()))
{
VerifyDecodedImage(img);
}
using var img = Image.Load<Rgba32>(this.Stream, out IImageFormat format);
VerifyDecodedImage(img);
Assert.IsType<BmpFormat>(format);
}
[Fact]
public void Stream_OutFormat_Agnostic()
{
using (var img = Image.Load(this.Stream, out IImageFormat format))
{
VerifyDecodedImage(img);
Assert.IsType<BmpFormat>(format);
}
using var img = Image.Load(this.Stream, out IImageFormat format);
VerifyDecodedImage(img);
Assert.IsType<BmpFormat>(format);
}
[Fact]
public async Task Async_Stream_OutFormat_Agnostic()
{
this.AllowSynchronousIO = false;
var formattedImage = await Image.LoadWithFormatAsync(this.Stream);
(Image Image, IImageFormat Format) formattedImage = await Image.LoadWithFormatAsync(this.Stream);
using (formattedImage.Image)
{
VerifyDecodedImage(formattedImage.Image);
@ -107,27 +79,23 @@ namespace SixLabors.ImageSharp.Tests
public async Task Async_Stream_Specific()
{
this.AllowSynchronousIO = false;
using (var img = await Image.LoadAsync<Rgba32>(this.Stream))
{
VerifyDecodedImage(img);
}
using Image<Rgba32> img = await Image.LoadAsync<Rgba32>(this.Stream);
VerifyDecodedImage(img);
}
[Fact]
public async Task Async_Stream_Agnostic()
{
this.AllowSynchronousIO = false;
using (var img = await Image.LoadAsync(this.Stream))
{
VerifyDecodedImage(img);
}
using Image img = await Image.LoadAsync(this.Stream);
VerifyDecodedImage(img);
}
[Fact]
public async Task Async_Stream_OutFormat_Specific()
{
this.AllowSynchronousIO = false;
var formattedImage = await Image.LoadWithFormatAsync<Rgba32>(this.Stream);
(Image<Rgba32> Image, IImageFormat Format) formattedImage = await Image.LoadWithFormatAsync<Rgba32>(this.Stream);
using (formattedImage.Image)
{
VerifyDecodedImage(formattedImage.Image);
@ -135,30 +103,7 @@ namespace SixLabors.ImageSharp.Tests
}
}
[Fact]
public async Task Async_Stream_Decoder_Specific()
{
this.AllowSynchronousIO = false;
using (var img = await Image.LoadAsync<Rgba32>(this.Stream, new BmpDecoder()))
{
VerifyDecodedImage(img);
}
}
[Fact]
public async Task Async_Stream_Decoder_Agnostic()
{
this.AllowSynchronousIO = false;
using (var img = await Image.LoadAsync(this.Stream, new BmpDecoder()))
{
VerifyDecodedImage(img);
}
}
public void Dispose()
{
this.BaseStream?.Dispose();
}
public void Dispose() => this.BaseStream?.Dispose();
}
}
}

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

Loading…
Cancel
Save