Browse Source

Merge branch 'main' into patch-1

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

13
.github/workflows/build-and-test.yml

@ -21,18 +21,19 @@ jobs:
runtime: -x64 runtime: -x64
codecov: false codecov: false
# Temp disabled due to runtime preview issues. # Temp disabled due to runtime preview issues.
# https://github.com/SixLabors/ImageSharp/issues/2117
#- os: macos-latest #- os: macos-latest
# framework: net7.0 # framework: net7.0
# sdk: 7.0.x # sdk: 7.0.x
# sdk-preview: true # sdk-preview: true
# runtime: -x64 # runtime: -x64
# codecov: false # codecov: false
- os: windows-latest #- os: windows-latest
framework: net7.0 # framework: net7.0
sdk: 7.0.x # sdk: 7.0.x
sdk-preview: true # sdk-preview: true
runtime: -x64 # runtime: -x64
codecov: false # codecov: false
- os: ubuntu-latest - os: ubuntu-latest
framework: net6.0 framework: net6.0
sdk: 6.0.x sdk: 6.0.x

22
src/ImageSharp/Advanced/AotCompilerTools.cs

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

5
src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs

@ -13,6 +13,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// </summary> /// </summary>
Pixel1 = 1, Pixel1 = 1,
/// <summary>
/// 2 bits per pixel.
/// </summary>
Pixel2 = 2,
/// <summary> /// <summary>
/// 4 bits per pixel. /// 4 bits per pixel.
/// </summary> /// </summary>

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

@ -10,43 +10,40 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <summary> /// <summary>
/// Image decoder for generating an image out of a Windows bitmap stream. /// Image decoder for generating an image out of a Windows bitmap stream.
/// </summary> /// </summary>
/// <remarks> public class BmpDecoder : IImageDecoderSpecialized<BmpDecoderOptions>
/// 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
{ {
/// <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/> /// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken) IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{ {
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
var decoder = new BmpDecoderCore(configuration, this); return new BmpDecoderCore(new() { GeneralOptions = options }).Identify(options.Configuration, stream, cancellationToken);
return decoder.Decode<TPixel>(configuration, stream, cancellationToken);
} }
/// <inheritdoc /> /// <inheritdoc/>
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) Image<TPixel> IImageDecoder.Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>(configuration, stream, 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/> /// <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)); 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);
} }
} }

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

@ -89,34 +89,38 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// </summary> /// </summary>
private BmpInfoHeader infoHeader; private BmpInfoHeader infoHeader;
/// <summary>
/// The global configuration.
/// </summary>
private readonly Configuration configuration;
/// <summary> /// <summary>
/// Used for allocating memory during processing operations. /// Used for allocating memory during processing operations.
/// </summary> /// </summary>
private readonly MemoryAllocator memoryAllocator; private readonly MemoryAllocator memoryAllocator;
/// <summary> /// <summary>
/// The bitmap decoder options. /// How to deal with skipped pixels,
/// which can occur during decoding run length encoded bitmaps.
/// </summary> /// </summary>
private readonly IBmpDecoderOptions options; private readonly RleSkippedPixelHandling rleSkippedPixelHandling;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BmpDecoderCore"/> class. /// Initializes a new instance of the <see cref="BmpDecoderCore"/> class.
/// </summary> /// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="options">The options.</param> /// <param name="options">The options.</param>
public BmpDecoderCore(Configuration configuration, IBmpDecoderOptions options) public BmpDecoderCore(BmpDecoderOptions options)
{ {
this.Configuration = configuration; this.Options = options.GeneralOptions;
this.memoryAllocator = configuration.MemoryAllocator; this.rleSkippedPixelHandling = options.RleSkippedPixelHandling;
this.options = options; this.configuration = options.GeneralOptions.Configuration;
this.memoryAllocator = this.configuration.MemoryAllocator;
} }
/// <inheritdoc /> /// <inheritdoc />
public Configuration Configuration { get; } public DecoderOptions Options { get; }
/// <summary> /// <inheritdoc />
/// Gets the dimensions of the image.
/// </summary>
public Size Dimensions => new(this.infoHeader.Width, this.infoHeader.Height); public Size Dimensions => new(this.infoHeader.Width, this.infoHeader.Height);
/// <inheritdoc /> /// <inheritdoc />
@ -128,7 +132,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{ {
int bytesPerColorMapEntry = this.ReadImageHeaders(stream, out bool inverted, out byte[] palette); 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(); Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer();
@ -325,7 +329,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
byte colorIdx = bufferRow[x]; byte colorIdx = bufferRow[x];
if (undefinedPixelsSpan[rowStartIdx + x]) if (undefinedPixelsSpan[rowStartIdx + x])
{ {
switch (this.options.RleSkippedPixelHandling) switch (this.rleSkippedPixelHandling)
{ {
case RleSkippedPixelHandling.FirstColorOfPalette: case RleSkippedPixelHandling.FirstColorOfPalette:
color.FromBgr24(Unsafe.As<byte, Bgr24>(ref colors[colorIdx * 4])); color.FromBgr24(Unsafe.As<byte, Bgr24>(ref colors[colorIdx * 4]));
@ -397,7 +401,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
int idx = rowStartIdx + (x * 3); int idx = rowStartIdx + (x * 3);
if (undefinedPixelsSpan[yMulWidth + x]) if (undefinedPixelsSpan[yMulWidth + x])
{ {
switch (this.options.RleSkippedPixelHandling) switch (this.rleSkippedPixelHandling)
{ {
case RleSkippedPixelHandling.FirstColorOfPalette: case RleSkippedPixelHandling.FirstColorOfPalette:
color.FromBgr24(Unsafe.As<byte, Bgr24>(ref bufferSpan[idx])); color.FromBgr24(Unsafe.As<byte, Bgr24>(ref bufferSpan[idx]));
@ -832,7 +836,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp
for (int y = 0; y < height; y++) for (int y = 0; y < height; y++)
{ {
int newY = Invert(y, height, inverted); int newY = Invert(y, height, inverted);
this.stream.Read(rowSpan); if (this.stream.Read(rowSpan) == 0)
{
BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!");
}
int offset = 0; int offset = 0;
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(newY); Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(newY);
@ -884,7 +892,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp
for (int y = 0; y < height; y++) for (int y = 0; y < height; y++)
{ {
this.stream.Read(bufferSpan); if (this.stream.Read(bufferSpan) == 0)
{
BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!");
}
int newY = Invert(y, height, inverted); int newY = Invert(y, height, inverted);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(newY); Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(newY);
@ -939,11 +951,15 @@ namespace SixLabors.ImageSharp.Formats.Bmp
for (int y = 0; y < height; y++) for (int y = 0; y < height; y++)
{ {
this.stream.Read(rowSpan); if (this.stream.Read(rowSpan) == 0)
{
BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!");
}
int newY = Invert(y, height, inverted); int newY = Invert(y, height, inverted);
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(newY); Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(newY);
PixelOperations<TPixel>.Instance.FromBgr24Bytes( PixelOperations<TPixel>.Instance.FromBgr24Bytes(
this.Configuration, this.configuration,
rowSpan, rowSpan,
pixelSpan, pixelSpan,
width); width);
@ -967,11 +983,15 @@ namespace SixLabors.ImageSharp.Formats.Bmp
for (int y = 0; y < height; y++) for (int y = 0; y < height; y++)
{ {
this.stream.Read(rowSpan); if (this.stream.Read(rowSpan) == 0)
{
BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!");
}
int newY = Invert(y, height, inverted); int newY = Invert(y, height, inverted);
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(newY); Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(newY);
PixelOperations<TPixel>.Instance.FromBgra32Bytes( PixelOperations<TPixel>.Instance.FromBgra32Bytes(
this.Configuration, this.configuration,
rowSpan, rowSpan,
pixelSpan, pixelSpan,
width); width);
@ -1003,10 +1023,13 @@ namespace SixLabors.ImageSharp.Formats.Bmp
// actually a BGRA image, and change tactics accordingly. // actually a BGRA image, and change tactics accordingly.
for (int y = 0; y < height; y++) for (int y = 0; y < height; y++)
{ {
this.stream.Read(rowSpan); if (this.stream.Read(rowSpan) == 0)
{
BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!");
}
PixelOperations<Bgra32>.Instance.FromBgra32Bytes( PixelOperations<Bgra32>.Instance.FromBgra32Bytes(
this.Configuration, this.configuration,
rowSpan, rowSpan,
bgraRowSpan, bgraRowSpan,
width); width);
@ -1036,13 +1059,16 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{ {
for (int y = 0; y < height; y++) for (int y = 0; y < height; y++)
{ {
this.stream.Read(rowSpan); if (this.stream.Read(rowSpan) == 0)
{
BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!");
}
int newY = Invert(y, height, inverted); int newY = Invert(y, height, inverted);
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(newY); Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(newY);
PixelOperations<TPixel>.Instance.FromBgra32Bytes( PixelOperations<TPixel>.Instance.FromBgra32Bytes(
this.Configuration, this.configuration,
rowSpan, rowSpan,
pixelSpan, pixelSpan,
width); width);
@ -1054,9 +1080,13 @@ namespace SixLabors.ImageSharp.Formats.Bmp
// Slow path. We need to set each alpha component value to fully opaque. // Slow path. We need to set each alpha component value to fully opaque.
for (int y = 0; y < height; y++) for (int y = 0; y < height; y++)
{ {
this.stream.Read(rowSpan); if (this.stream.Read(rowSpan) == 0)
{
BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!");
}
PixelOperations<Bgra32>.Instance.FromBgra32Bytes( PixelOperations<Bgra32>.Instance.FromBgra32Bytes(
this.Configuration, this.configuration,
rowSpan, rowSpan,
bgraRowSpan, bgraRowSpan,
width); width);
@ -1115,7 +1145,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp
for (int y = 0; y < height; y++) for (int y = 0; y < height; y++)
{ {
this.stream.Read(bufferSpan); if (this.stream.Read(bufferSpan) == 0)
{
BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!");
}
int newY = Invert(y, height, inverted); int newY = Invert(y, height, inverted);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(newY); Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(newY);
@ -1387,7 +1421,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
int colorMapSizeBytes = -1; int colorMapSizeBytes = -1;
if (this.infoHeader.ClrUsed == 0) if (this.infoHeader.ClrUsed == 0)
{ {
if (this.infoHeader.BitsPerPixel is 1 or 4 or 8) if (this.infoHeader.BitsPerPixel is 1 or 2 or 4 or 8)
{ {
switch (this.fileMarkerType) switch (this.fileMarkerType)
{ {
@ -1431,7 +1465,10 @@ namespace SixLabors.ImageSharp.Formats.Bmp
palette = new byte[colorMapSizeBytes]; palette = new byte[colorMapSizeBytes];
this.stream.Read(palette, 0, colorMapSizeBytes); if (this.stream.Read(palette, 0, colorMapSizeBytes) == 0)
{
BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for the palette!");
}
} }
this.infoHeader.VerifyDimensions(); this.infoHeader.VerifyDimensions();

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; }
}
}

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

@ -57,6 +57,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// </summary> /// </summary>
private const int ColorPaletteSize4Bit = 64; private const int ColorPaletteSize4Bit = 64;
/// <summary>
/// The color palette for an 2 bit image will have 4 entry's with 4 bytes for each entry.
/// </summary>
private const int ColorPaletteSize2Bit = 16;
/// <summary> /// <summary>
/// The color palette for an 1 bit image will have 2 entry's with 4 bytes for each entry. /// The color palette for an 1 bit image will have 2 entry's with 4 bytes for each entry.
/// </summary> /// </summary>
@ -125,19 +130,14 @@ namespace SixLabors.ImageSharp.Formats.Bmp
int bytesPerLine = 4 * (((image.Width * bpp) + 31) / 32); int bytesPerLine = 4 * (((image.Width * bpp) + 31) / 32);
this.padding = bytesPerLine - (int)(image.Width * (bpp / 8F)); this.padding = bytesPerLine - (int)(image.Width * (bpp / 8F));
int colorPaletteSize = 0; int colorPaletteSize = this.bitsPerPixel switch
if (this.bitsPerPixel == BmpBitsPerPixel.Pixel8)
{
colorPaletteSize = ColorPaletteSize8Bit;
}
else if (this.bitsPerPixel == BmpBitsPerPixel.Pixel4)
{
colorPaletteSize = ColorPaletteSize4Bit;
}
else if (this.bitsPerPixel == BmpBitsPerPixel.Pixel1)
{ {
colorPaletteSize = ColorPaletteSize1Bit; BmpBitsPerPixel.Pixel8 => ColorPaletteSize8Bit,
} BmpBitsPerPixel.Pixel4 => ColorPaletteSize4Bit,
BmpBitsPerPixel.Pixel2 => ColorPaletteSize2Bit,
BmpBitsPerPixel.Pixel1 => ColorPaletteSize1Bit,
_ => 0
};
byte[] iccProfileData = null; byte[] iccProfileData = null;
int iccProfileSize = 0; int iccProfileSize = 0;
@ -322,27 +322,31 @@ namespace SixLabors.ImageSharp.Formats.Bmp
switch (this.bitsPerPixel) switch (this.bitsPerPixel)
{ {
case BmpBitsPerPixel.Pixel32: case BmpBitsPerPixel.Pixel32:
this.Write32Bit(stream, pixels); this.Write32BitPixelData(stream, pixels);
break; break;
case BmpBitsPerPixel.Pixel24: case BmpBitsPerPixel.Pixel24:
this.Write24Bit(stream, pixels); this.Write24BitPixelData(stream, pixels);
break; break;
case BmpBitsPerPixel.Pixel16: case BmpBitsPerPixel.Pixel16:
this.Write16Bit(stream, pixels); this.Write16BitPixelData(stream, pixels);
break; break;
case BmpBitsPerPixel.Pixel8: case BmpBitsPerPixel.Pixel8:
this.Write8Bit(stream, image); this.Write8BitPixelData(stream, image);
break; break;
case BmpBitsPerPixel.Pixel4: case BmpBitsPerPixel.Pixel4:
this.Write4BitColor(stream, image); this.Write4BitPixelData(stream, image);
break;
case BmpBitsPerPixel.Pixel2:
this.Write2BitPixelData(stream, image);
break; break;
case BmpBitsPerPixel.Pixel1: case BmpBitsPerPixel.Pixel1:
this.Write1BitColor(stream, image); this.Write1BitPixelData(stream, image);
break; break;
} }
} }
@ -351,12 +355,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp
=> this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, this.padding); => this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, this.padding);
/// <summary> /// <summary>
/// Writes the 32bit color palette to the stream. /// Writes 32-bit data with a color palette to the stream.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param> /// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> containing pixel data.</param> /// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> containing pixel data.</param>
private void Write32Bit<TPixel>(Stream stream, Buffer2D<TPixel> pixels) private void Write32BitPixelData<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using IMemoryOwner<byte> row = this.AllocateRow(pixels.Width, 4); using IMemoryOwner<byte> row = this.AllocateRow(pixels.Width, 4);
@ -375,12 +379,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp
} }
/// <summary> /// <summary>
/// Writes the 24bit color palette to the stream. /// Writes 24-bit pixel data with a color palette to the stream.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param> /// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> containing pixel data.</param> /// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> containing pixel data.</param>
private void Write24Bit<TPixel>(Stream stream, Buffer2D<TPixel> pixels) private void Write24BitPixelData<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
int width = pixels.Width; int width = pixels.Width;
@ -401,12 +405,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp
} }
/// <summary> /// <summary>
/// Writes the 16bit color palette to the stream. /// Writes 16-bit pixel data with a color palette to the stream.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam> /// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param> /// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> containing pixel data.</param> /// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> containing pixel data.</param>
private void Write16Bit<TPixel>(Stream stream, Buffer2D<TPixel> pixels) private void Write16BitPixelData<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
int width = pixels.Width; int width = pixels.Width;
@ -429,12 +433,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp
} }
/// <summary> /// <summary>
/// Writes an 8 bit image with a color palette. The color palette has 256 entry's with 4 bytes for each entry. /// Writes 8 bit pixel data with a color palette. The color palette has 256 entry's with 4 bytes for each entry.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam> /// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param> /// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param> /// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param>
private void Write8Bit<TPixel>(Stream stream, ImageFrame<TPixel> image) private void Write8BitPixelData<TPixel>(Stream stream, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
bool isL8 = typeof(TPixel) == typeof(L8); bool isL8 = typeof(TPixel) == typeof(L8);
@ -443,7 +447,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
if (isL8) if (isL8)
{ {
this.Write8BitGray(stream, image, colorPalette); this.Write8BitPixelData(stream, image, colorPalette);
} }
else else
{ {
@ -480,13 +484,13 @@ namespace SixLabors.ImageSharp.Formats.Bmp
} }
/// <summary> /// <summary>
/// Writes an 8 bit gray image with a color palette. The color palette has 256 entry's with 4 bytes for each entry. /// Writes 8 bit gray pixel data with a color palette. The color palette has 256 entry's with 4 bytes for each entry.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam> /// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param> /// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param> /// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param>
/// <param name="colorPalette">A byte span of size 1024 for the color palette.</param> /// <param name="colorPalette">A byte span of size 1024 for the color palette.</param>
private void Write8BitGray<TPixel>(Stream stream, ImageFrame<TPixel> image, Span<byte> colorPalette) private void Write8BitPixelData<TPixel>(Stream stream, ImageFrame<TPixel> image, Span<byte> colorPalette)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
// Create a color palette with 256 different gray values. // Create a color palette with 256 different gray values.
@ -518,12 +522,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp
} }
/// <summary> /// <summary>
/// Writes an 4 bit color image with a color palette. The color palette has 16 entry's with 4 bytes for each entry. /// Writes 4 bit pixel data with a color palette. The color palette has 16 entry's with 4 bytes for each entry.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam> /// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param> /// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param> /// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param>
private void Write4BitColor<TPixel>(Stream stream, ImageFrame<TPixel> image) private void Write4BitPixelData<TPixel>(Stream stream, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, new QuantizerOptions() using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, new QuantizerOptions()
@ -562,12 +566,65 @@ namespace SixLabors.ImageSharp.Formats.Bmp
} }
/// <summary> /// <summary>
/// Writes a 1 bit image with a color palette. The color palette has 2 entry's with 4 bytes for each entry. /// Writes 2 bit pixel data with a color palette. The color palette has 4 entry's with 4 bytes for each entry.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param>
private void Write2BitPixelData<TPixel>(Stream stream, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, new QuantizerOptions()
{
MaxColors = 4
});
using IndexedImageFrame<TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds());
using IMemoryOwner<byte> colorPaletteBuffer = this.memoryAllocator.Allocate<byte>(ColorPaletteSize2Bit, AllocationOptions.Clean);
Span<byte> colorPalette = colorPaletteBuffer.GetSpan();
ReadOnlySpan<TPixel> quantizedColorPalette = quantized.Palette.Span;
this.WriteColorPalette(stream, quantizedColorPalette, colorPalette);
ReadOnlySpan<byte> pixelRowSpan = quantized.DangerousGetRowSpan(0);
int rowPadding = pixelRowSpan.Length % 4 != 0 ? this.padding - 1 : this.padding;
for (int y = image.Height - 1; y >= 0; y--)
{
pixelRowSpan = quantized.DangerousGetRowSpan(y);
int endIdx = pixelRowSpan.Length % 4 == 0 ? pixelRowSpan.Length : pixelRowSpan.Length - 4;
int i = 0;
for (i = 0; i < endIdx; i += 4)
{
stream.WriteByte((byte)((pixelRowSpan[i] << 6) | (pixelRowSpan[i + 1] << 4) | (pixelRowSpan[i + 2] << 2) | pixelRowSpan[i + 3]));
}
if (pixelRowSpan.Length % 4 != 0)
{
int shift = 6;
byte pixelData = 0;
for (; i < pixelRowSpan.Length; i++)
{
pixelData = (byte)(pixelData | (pixelRowSpan[i] << shift));
shift -= 2;
}
stream.WriteByte(pixelData);
}
for (i = 0; i < rowPadding; i++)
{
stream.WriteByte(0);
}
}
}
/// <summary>
/// Writes 1 bit pixel data with a color palette. The color palette has 2 entry's with 4 bytes for each entry.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam> /// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param> /// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param> /// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param>
private void Write1BitColor<TPixel>(Stream stream, ImageFrame<TPixel> image) private void Write1BitPixelData<TPixel>(Stream stream, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, new QuantizerOptions() using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, new QuantizerOptions()
@ -622,7 +679,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
Span<uint> colorPaletteAsUInt = MemoryMarshal.Cast<byte, uint>(colorPalette); Span<uint> colorPaletteAsUInt = MemoryMarshal.Cast<byte, uint>(colorPalette);
for (int i = 0; i < colorPaletteAsUInt.Length; i++) for (int i = 0; i < colorPaletteAsUInt.Length; i++)
{ {
colorPaletteAsUInt[i] = colorPaletteAsUInt[i] & 0x00FFFFFF; // Padding byte, always 0. colorPaletteAsUInt[i] &= 0x00FFFFFF; // Padding byte, always 0.
} }
stream.Write(colorPalette); stream.Write(colorPalette);

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.IO;
using System.Threading; using System.Threading;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Gif namespace SixLabors.ImageSharp.Formats.Gif
@ -11,37 +10,33 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary> /// <summary>
/// Decoder for generating an image out of a gif encoded stream. /// Decoder for generating an image out of a gif encoded stream.
/// </summary> /// </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/> /// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken) IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{ {
var decoder = new GifDecoderCore(configuration, this); Guard.NotNull(options, nameof(options));
return decoder.Decode<TPixel>(configuration, stream, cancellationToken); Guard.NotNull(stream, nameof(stream));
}
/// <inheritdoc /> return new GifDecoderCore(options).Identify(options.Configuration, stream, cancellationToken);
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) }
=> this.Decode<Rgba32>(configuration, stream, cancellationToken);
/// <inheritdoc/> /// <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)); Guard.NotNull(stream, nameof(stream));
var decoder = new GifDecoderCore(configuration, this); GifDecoderCore decoder = new(options);
return decoder.Identify(configuration, stream, cancellationToken); 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> /// </summary>
private GifImageDescriptor imageDescriptor; 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> /// <summary>
/// The abstract metadata. /// The abstract metadata.
/// </summary> /// </summary>
@ -69,39 +89,27 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="GifDecoderCore"/> class. /// Initializes a new instance of the <see cref="GifDecoderCore"/> class.
/// </summary> /// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="options">The decoder options.</param> /// <param name="options">The decoder options.</param>
public GifDecoderCore(Configuration configuration, IGifDecoderOptions options) public GifDecoderCore(DecoderOptions options)
{ {
this.IgnoreMetadata = options.IgnoreMetadata; this.Options = options;
this.DecodingMode = options.DecodingMode; this.configuration = options.Configuration;
this.Configuration = configuration ?? Configuration.Default; this.skipMetadata = options.SkipMetadata;
this.maxFrames = options.MaxFrames;
this.memoryAllocator = this.configuration.MemoryAllocator;
} }
/// <inheritdoc /> /// <inheritdoc />
public Configuration Configuration { get; } public DecoderOptions Options { 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; }
/// <summary> /// <inheritdoc />
/// Gets the decoding mode for multi-frame images.
/// </summary>
public FrameDecodingMode DecodingMode { get; }
/// <summary>
/// Gets the dimensions of the image.
/// </summary>
public Size Dimensions => new(this.imageDescriptor.Width, this.imageDescriptor.Height); public Size Dimensions => new(this.imageDescriptor.Width, this.imageDescriptor.Height);
private MemoryAllocator MemoryAllocator => this.Configuration.MemoryAllocator;
/// <inheritdoc /> /// <inheritdoc />
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken) public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
uint frameCount = 0;
Image<TPixel> image = null; Image<TPixel> image = null;
ImageFrame<TPixel> previousFrame = null; ImageFrame<TPixel> previousFrame = null;
try try
@ -114,7 +122,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
{ {
if (nextFlag == GifConstants.ImageLabel) if (nextFlag == GifConstants.ImageLabel)
{ {
if (previousFrame != null && this.DecodingMode == FrameDecodingMode.First) if (previousFrame != null && ++frameCount == this.maxFrames)
{ {
break; break;
} }
@ -277,9 +285,9 @@ namespace SixLabors.ImageSharp.Formats.Gif
this.stream.Read(this.buffer, 0, GifConstants.ApplicationBlockSize); this.stream.Read(this.buffer, 0, GifConstants.ApplicationBlockSize);
bool isXmp = this.buffer.AsSpan().StartsWith(GifConstants.XmpApplicationIdentificationBytes); 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) if (extension.Data.Length > 0)
{ {
this.metadata.XmpProfile = new XmpProfile(extension.Data); 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"); 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); this.stream.Seek(length, SeekOrigin.Current);
continue; continue;
} }
using IMemoryOwner<byte> commentsBuffer = this.MemoryAllocator.Allocate<byte>(length); using IMemoryOwner<byte> commentsBuffer = this.memoryAllocator.Allocate<byte>(length);
Span<byte> commentsSpan = commentsBuffer.GetSpan(); Span<byte> commentsSpan = commentsBuffer.GetSpan();
this.stream.Read(commentsSpan); this.stream.Read(commentsSpan);
@ -385,11 +393,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
if (this.imageDescriptor.LocalColorTableFlag) if (this.imageDescriptor.LocalColorTableFlag)
{ {
int length = this.imageDescriptor.LocalColorTableSize * 3; 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()); 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); this.ReadFrameIndices(indices);
Span<byte> rawColorTable = default; Span<byte> rawColorTable = default;
@ -423,7 +431,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
private void ReadFrameIndices(Buffer2D<byte> indices) private void ReadFrameIndices(Buffer2D<byte> indices)
{ {
int minCodeSize = this.stream.ReadByte(); 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); lzwDecoder.DecodePixels(minCodeSize, indices);
} }
@ -451,12 +459,12 @@ namespace SixLabors.ImageSharp.Formats.Gif
{ {
if (!transFlag) 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 else
{ {
// This initializes the image to become fully transparent because the alpha channel is zero. // 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); this.SetFrameMetadata(image.Frames.RootFrame.Metadata);
@ -675,7 +683,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
if (globalColorTableLength > 0) 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 // Read the global color table data from the stream
stream.Read(this.globalColorTable.GetSpan()); 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> /// <summary>
/// Encapsulates properties and methods required for decoding an image from a stream. /// Encapsulates properties and methods required for decoding an image from a stream.
/// </summary> /// </summary>
public interface IImageDecoder public interface IImageDecoder : IImageInfoDetector
{ {
/// <summary> /// <summary>
/// Decodes the image from the specified stream to an <see cref="Image{TPixel}"/> of a specific pixel type. /// Decodes the image from the specified stream to an <see cref="Image{TPixel}"/> of a specific pixel type.
/// </summary> /// </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> /// <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="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns> /// <returns>The <see cref="Image{TPixel}"/>.</returns>
// TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110) /// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken) Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>; where TPixel : unmanaged, IPixel<TPixel>;
/// <summary> /// <summary>
/// Decodes the image from the specified stream to an <see cref="Image"/>. /// Decodes the image from the specified stream to an <see cref="Image"/>.
/// </summary> /// </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="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>The <see cref="Image"/>.</returns> /// <returns>The <see cref="Image"/>.</returns>
// TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110) /// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken); 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. // Licensed under the Six Labors Split License.
using System; using System;
@ -9,14 +9,14 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats namespace SixLabors.ImageSharp.Formats
{ {
/// <summary> /// <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> /// </summary>
internal interface IImageDecoderInternals internal interface IImageDecoderInternals
{ {
/// <summary> /// <summary>
/// Gets the associated configuration. /// Gets the general decoder options.
/// </summary> /// </summary>
Configuration Configuration { get; } DecoderOptions Options { get; }
/// <summary> /// <summary>
/// Gets the dimensions of the image being decoded. /// 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> /// <summary>
/// Reads the raw image information from the specified stream. /// Reads the raw image information from the specified stream.
/// </summary> /// </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="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>The <see cref="PixelTypeInfo"/> object</returns> /// <returns>The <see cref="IImageInfo"/> object.</returns>
IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken); /// <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.IO;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
namespace SixLabors.ImageSharp.Formats namespace SixLabors.ImageSharp.Formats
{ {
/// <summary>
/// Utility methods for <see cref="IImageDecoder"/>.
/// </summary>
internal static class ImageDecoderUtilities 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, this IImageDecoderInternals decoder,
Configuration configuration, Configuration configuration,
Stream stream, 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, this IImageDecoderInternals decoder,
Configuration configuration, Configuration configuration,
Stream stream, Stream stream,
@ -38,7 +81,7 @@ namespace SixLabors.ImageSharp.Formats
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
=> decoder.Decode<TPixel>(configuration, stream, DefaultLargeImageExceptionFactory, cancellationToken); => decoder.Decode<TPixel>(configuration, stream, DefaultLargeImageExceptionFactory, cancellationToken);
public static Image<TPixel> Decode<TPixel>( internal static Image<TPixel> Decode<TPixel>(
this IImageDecoderInternals decoder, this IImageDecoderInternals decoder,
Configuration configuration, Configuration configuration,
Stream stream, 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.IO;
using System.Threading; using System.Threading;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg namespace SixLabors.ImageSharp.Formats.Jpeg
{ {
/// <summary> /// <summary>
/// Image decoder for generating an image out of a jpg stream. /// Decoder for generating an image out of a jpeg encoded stream.
/// </summary> /// </summary>
public sealed class JpegDecoder : IImageDecoder, IJpegDecoderOptions, IImageInfoDetector public sealed class JpegDecoder : IImageDecoderSpecialized<JpegDecoderOptions>
{ {
/// <inheritdoc/> /// <inheritdoc/>
public bool IgnoreMetadata { get; set; } IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{ {
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
using var decoder = new JpegDecoderCore(configuration, this); using JpegDecoderCore decoder = new(new() { GeneralOptions = options });
return decoder.Decode<TPixel>(configuration, stream, cancellationToken); return decoder.Identify(options.Configuration, stream, cancellationToken);
} }
/// <inheritdoc /> /// <inheritdoc/>
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) Image<TPixel> IImageDecoder.Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgb24>(configuration, stream, cancellationToken); => ((IImageDecoderSpecialized<JpegDecoderOptions>)this).Decode<TPixel>(new() { GeneralOptions = options }, stream, cancellationToken);
/// <summary> /// <inheritdoc/>
/// Decodes and downscales the image from the specified stream if possible. Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
/// </summary> => ((IImageDecoderSpecialized<JpegDecoderOptions>)this).Decode(new() { GeneralOptions = options }, stream, cancellationToken);
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="configuration">Configuration.</param> /// <inheritdoc/>
/// <param name="stream">Stream.</param> Image<TPixel> IImageDecoderSpecialized<JpegDecoderOptions>.Decode<TPixel>(JpegDecoderOptions options, Stream stream, CancellationToken cancellationToken)
/// <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>
{ {
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
using var decoder = new JpegDecoderCore(configuration, this); using JpegDecoderCore decoder = new(options);
using var bufferedReadStream = new BufferedReadStream(configuration, stream); Image<TPixel> image = decoder.Decode<TPixel>(options.GeneralOptions.Configuration, stream, cancellationToken);
try
{ if (options.ResizeMode != JpegDecoderResizeMode.IdctOnly)
return decoder.DecodeInto<TPixel>(bufferedReadStream, targetSize, cancellationToken);
}
catch (InvalidMemoryOperationException ex)
{ {
throw new InvalidImageContentException(((IImageDecoderInternals)decoder).Dimensions, ex); ImageDecoderUtilities.Resize(options.GeneralOptions, image);
} }
return image;
} }
/// <inheritdoc/> /// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken) Image IImageDecoderSpecialized<JpegDecoderOptions>.Decode(JpegDecoderOptions options, Stream stream, CancellationToken cancellationToken)
{ => ((IImageDecoderSpecialized<JpegDecoderOptions>)this).Decode<Rgb24>(options, stream, cancellationToken);
Guard.NotNull(stream, nameof(stream));
using var decoder = new JpegDecoderCore(configuration, this);
return decoder.Identify(configuration, stream, cancellationToken);
}
} }
} }

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

@ -116,31 +116,42 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
private int? resetInterval; private int? resetInterval;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="JpegDecoderCore" /> class. /// The global configuration.
/// </summary> /// </summary>
/// <param name="configuration">The configuration.</param> private readonly Configuration configuration;
/// <param name="options">The options.</param>
public JpegDecoderCore(Configuration configuration, IJpegDecoderOptions options)
{
this.Configuration = configuration ?? Configuration.Default;
this.IgnoreMetadata = options.IgnoreMetadata;
}
/// <inheritdoc /> /// <summary>
public Configuration Configuration { get; } /// Whether to skip metadata during decode.
/// </summary>
private readonly bool skipMetadata;
/// <summary> /// <summary>
/// Gets the frame /// The jpeg specific resize options.
/// </summary> /// </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/> /// <inheritdoc/>
Size IImageDecoderInternals.Dimensions => this.Frame.PixelSize; public Size Dimensions => this.Frame.PixelSize;
/// <summary> /// <summary>
/// Gets a value indicating whether the metadata should be ignored when the image is being decoded. /// Gets the frame
/// </summary> /// </summary>
public bool IgnoreMetadata { get; } public JpegFrame Frame { get; private set; }
/// <summary> /// <summary>
/// Gets the <see cref="ImageMetadata"/> decoded by this decoder instance. /// Gets the <see cref="ImageMetadata"/> decoded by this decoder instance.
@ -201,48 +212,33 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <inheritdoc/> /// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken) public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> 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.InitExifProfile();
this.InitIccProfile(); this.InitIccProfile();
this.InitIptcProfile(); this.InitIptcProfile();
this.InitXmpProfile(); this.InitXmpProfile();
this.InitDerivedMetadataProperties(); this.InitDerivedMetadataProperties();
Size pixelSize = this.Frame.PixelSize; return new Image<TPixel>(
return new ImageInfo(new PixelTypeInfo(this.Frame.BitsPerPixel), pixelSize.Width, pixelSize.Height, this.Metadata); this.configuration,
spectralConverter.GetPixelBuffer(cancellationToken),
this.Metadata);
} }
/// <summary> /// <inheritdoc/>
/// Decodes and downscales the image from the specified stream if possible. public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
/// </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>
{ {
using var spectralConverter = new SpectralConverter<TPixel>(this.Configuration, targetSize); this.ParseStream(stream, spectralConverter: null, cancellationToken);
this.ParseStream(stream, spectralConverter, cancellationToken);
this.InitExifProfile(); this.InitExifProfile();
this.InitIccProfile(); this.InitIccProfile();
this.InitIptcProfile(); this.InitIptcProfile();
this.InitXmpProfile(); this.InitXmpProfile();
this.InitDerivedMetadataProperties(); this.InitDerivedMetadataProperties();
return new Image<TPixel>( Size pixelSize = this.Frame.PixelSize;
this.Configuration, return new ImageInfo(new PixelTypeInfo(this.Frame.BitsPerPixel), pixelSize.Width, pixelSize.Height, this.Metadata);
spectralConverter.GetPixelBuffer(cancellationToken),
this.Metadata);
} }
/// <summary> /// <summary>
@ -262,7 +258,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
} }
using var ms = new MemoryStream(tableBytes); 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. // Check for the Start Of Image marker.
int bytesRead = stream.Read(this.markerBuffer, 0, 2); int bytesRead = stream.Read(this.markerBuffer, 0, 2);
@ -778,7 +774,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{ {
const int ExifMarkerLength = 6; const int ExifMarkerLength = 6;
const int XmpMarkerLength = 29; const int XmpMarkerLength = 29;
if (remaining < ExifMarkerLength || this.IgnoreMetadata) if (remaining < ExifMarkerLength || this.skipMetadata)
{ {
// Skip the application header length. // Skip the application header length.
stream.Skip(remaining); stream.Skip(remaining);
@ -816,7 +812,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
if (ProfileResolver.IsProfile(this.temp, ProfileResolver.XmpMarker.Slice(0, ExifMarkerLength))) if (ProfileResolver.IsProfile(this.temp, ProfileResolver.XmpMarker.Slice(0, ExifMarkerLength)))
{ {
const int remainingXmpMarkerBytes = XmpMarkerLength - ExifMarkerLength; const int remainingXmpMarkerBytes = XmpMarkerLength - ExifMarkerLength;
if (remaining < remainingXmpMarkerBytes || this.IgnoreMetadata) if (remaining < remainingXmpMarkerBytes || this.skipMetadata)
{ {
// Skip the application header length. // Skip the application header length.
stream.Skip(remaining); stream.Skip(remaining);
@ -858,7 +854,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{ {
// Length is 14 though we only need to check 12. // Length is 14 though we only need to check 12.
const int Icclength = 14; const int Icclength = 14;
if (remaining < Icclength || this.IgnoreMetadata) if (remaining < Icclength || this.skipMetadata)
{ {
stream.Skip(remaining); stream.Skip(remaining);
return; return;
@ -899,7 +895,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <param name="remaining">The remaining bytes in the segment block.</param> /// <param name="remaining">The remaining bytes in the segment block.</param>
private void ProcessApp13Marker(BufferedReadStream stream, int remaining) private void ProcessApp13Marker(BufferedReadStream stream, int remaining)
{ {
if (remaining < ProfileResolver.AdobePhotoshopApp13Marker.Length || this.IgnoreMetadata) if (remaining < ProfileResolver.AdobePhotoshopApp13Marker.Length || this.skipMetadata)
{ {
stream.Skip(remaining); stream.Skip(remaining);
return; return;
@ -1283,8 +1279,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
} }
IJpegComponent component = decodingComponentType is ComponentType.Huffman ? IJpegComponent component = decodingComponentType is ComponentType.Huffman ?
new JpegComponent(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); new ArithmeticDecodingComponent(this.configuration.MemoryAllocator, this.Frame, componentId, h, v, quantTableIndex, i);
this.Frame.Components[i] = (JpegComponent)component; this.Frame.Components[i] = (JpegComponent)component;
this.Frame.ComponentIds[i] = componentId; this.Frame.ComponentIds[i] = componentId;
@ -1323,7 +1319,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
} }
int length = remaining; 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> bufferSpan = buffer.GetSpan();
Span<byte> huffmanLengthsSpan = bufferSpan.Slice(0, codeLengthsByteSize); 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> /// </list>
/// The specification of these images is found at <seealso href="http://netpbm.sourceforge.net/doc/pnm.html"/>. /// The specification of these images is found at <seealso href="http://netpbm.sourceforge.net/doc/pnm.html"/>.
/// </summary> /// </summary>
public sealed class PbmDecoder : IImageDecoder, IImageInfoDetector public sealed class PbmDecoder : IImageDecoder
{ {
/// <inheritdoc/> /// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken) IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{ {
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
var decoder = new PbmDecoderCore(configuration); return new PbmDecoderCore(options).Identify(options.Configuration, stream, cancellationToken);
return decoder.Decode<TPixel>(configuration, stream, cancellationToken);
} }
/// <inheritdoc /> /// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) Image<TPixel> IImageDecoder.Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgb24>(configuration, stream, cancellationToken);
/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{ {
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
var decoder = new PbmDecoderCore(configuration); PbmDecoderCore decoder = new(options);
return decoder.Identify(configuration, stream, cancellationToken); 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; private int maxPixelValue;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PbmDecoderCore" /> class. /// The general configuration.
/// </summary> /// </summary>
/// <param name="configuration">The configuration.</param> private readonly Configuration configuration;
public PbmDecoderCore(Configuration configuration) => this.Configuration = configuration ?? Configuration.Default;
/// <inheritdoc /> /// <summary>
public Configuration Configuration { get; } /// The colortype to use
/// </summary>
private PbmColorType colorType;
/// <summary> /// <summary>
/// Gets the colortype to use /// The size of the pixel array
/// </summary> /// </summary>
public PbmColorType ColorType { get; private set; } private Size pixelSize;
/// <summary> /// <summary>
/// Gets the size of the pixel array /// The component data type
/// </summary> /// </summary>
public Size PixelSize { get; private set; } private PbmComponentType componentType;
/// <summary> /// <summary>
/// Gets the component data type /// The Encoding of pixels
/// </summary> /// </summary>
public PbmComponentType ComponentType { get; private set; } private PbmEncoding encoding;
/// <summary> /// <summary>
/// Gets the Encoding of pixels /// The <see cref="ImageMetadata"/> decoded by this decoder instance.
/// </summary> /// </summary>
public PbmEncoding Encoding { get; private set; } private ImageMetadata metadata;
/// <summary> /// <summary>
/// Gets the <see cref="ImageMetadata"/> decoded by this decoder instance. /// Initializes a new instance of the <see cref="PbmDecoderCore" /> class.
/// </summary> /// </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/> /// <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/> /// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken) public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
@ -63,12 +70,12 @@ namespace SixLabors.ImageSharp.Formats.Pbm
{ {
this.ProcessHeader(stream); 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(); Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer();
this.ProcessPixels(stream, pixels); this.ProcessPixels(stream, pixels);
if (this.NeedsUpscaling) if (this.NeedsUpscaling())
{ {
this.ProcessUpscaling(image); this.ProcessUpscaling(image);
} }
@ -82,8 +89,8 @@ namespace SixLabors.ImageSharp.Formats.Pbm
this.ProcessHeader(stream); this.ProcessHeader(stream);
// BlackAndWhite pixels are encoded into a byte. // BlackAndWhite pixels are encoded into a byte.
int bitsPerPixel = this.ComponentType == PbmComponentType.Short ? 16 : 8; int bitsPerPixel = this.componentType == PbmComponentType.Short ? 16 : 8;
return new ImageInfo(new PixelTypeInfo(bitsPerPixel), this.PixelSize.Width, this.PixelSize.Height, this.Metadata); return new ImageInfo(new PixelTypeInfo(bitsPerPixel), this.pixelSize.Width, this.pixelSize.Height, this.metadata);
} }
/// <summary> /// <summary>
@ -104,33 +111,33 @@ namespace SixLabors.ImageSharp.Formats.Pbm
{ {
case '1': case '1':
// Plain PBM format: 1 component per pixel, boolean value ('0' or '1'). // Plain PBM format: 1 component per pixel, boolean value ('0' or '1').
this.ColorType = PbmColorType.BlackAndWhite; this.colorType = PbmColorType.BlackAndWhite;
this.Encoding = PbmEncoding.Plain; this.encoding = PbmEncoding.Plain;
break; break;
case '2': case '2':
// Plain PGM format: 1 component per pixel, in decimal text. // Plain PGM format: 1 component per pixel, in decimal text.
this.ColorType = PbmColorType.Grayscale; this.colorType = PbmColorType.Grayscale;
this.Encoding = PbmEncoding.Plain; this.encoding = PbmEncoding.Plain;
break; break;
case '3': case '3':
// Plain PPM format: 3 components per pixel, in decimal text. // Plain PPM format: 3 components per pixel, in decimal text.
this.ColorType = PbmColorType.Rgb; this.colorType = PbmColorType.Rgb;
this.Encoding = PbmEncoding.Plain; this.encoding = PbmEncoding.Plain;
break; break;
case '4': case '4':
// Binary PBM format: 1 component per pixel, 8 pixels per byte. // Binary PBM format: 1 component per pixel, 8 pixels per byte.
this.ColorType = PbmColorType.BlackAndWhite; this.colorType = PbmColorType.BlackAndWhite;
this.Encoding = PbmEncoding.Binary; this.encoding = PbmEncoding.Binary;
break; break;
case '5': case '5':
// Binary PGM format: 1 components per pixel, in binary integers. // Binary PGM format: 1 components per pixel, in binary integers.
this.ColorType = PbmColorType.Grayscale; this.colorType = PbmColorType.Grayscale;
this.Encoding = PbmEncoding.Binary; this.encoding = PbmEncoding.Binary;
break; break;
case '6': case '6':
// Binary PPM format: 3 components per pixel, in binary integers. // Binary PPM format: 3 components per pixel, in binary integers.
this.ColorType = PbmColorType.Rgb; this.colorType = PbmColorType.Rgb;
this.Encoding = PbmEncoding.Binary; this.encoding = PbmEncoding.Binary;
break; break;
case '7': case '7':
// PAM image: sequence of images. // PAM image: sequence of images.
@ -144,52 +151,54 @@ namespace SixLabors.ImageSharp.Formats.Pbm
stream.SkipWhitespaceAndComments(); stream.SkipWhitespaceAndComments();
int height = stream.ReadDecimal(); int height = stream.ReadDecimal();
stream.SkipWhitespaceAndComments(); stream.SkipWhitespaceAndComments();
if (this.ColorType != PbmColorType.BlackAndWhite) if (this.colorType != PbmColorType.BlackAndWhite)
{ {
this.maxPixelValue = stream.ReadDecimal(); this.maxPixelValue = stream.ReadDecimal();
if (this.maxPixelValue > 255) if (this.maxPixelValue > 255)
{ {
this.ComponentType = PbmComponentType.Short; this.componentType = PbmComponentType.Short;
} }
else else
{ {
this.ComponentType = PbmComponentType.Byte; this.componentType = PbmComponentType.Byte;
} }
stream.SkipWhitespaceAndComments(); stream.SkipWhitespaceAndComments();
} }
else else
{ {
this.ComponentType = PbmComponentType.Bit; this.componentType = PbmComponentType.Bit;
} }
this.PixelSize = new Size(width, height); this.pixelSize = new Size(width, height);
this.Metadata = new ImageMetadata(); this.metadata = new ImageMetadata();
PbmMetadata meta = this.Metadata.GetPbmMetadata(); PbmMetadata meta = this.metadata.GetPbmMetadata();
meta.Encoding = this.Encoding; meta.Encoding = this.encoding;
meta.ColorType = this.ColorType; meta.ColorType = this.colorType;
meta.ComponentType = this.ComponentType; meta.ComponentType = this.componentType;
} }
private void ProcessPixels<TPixel>(BufferedReadStream stream, Buffer2D<TPixel> pixels) private void ProcessPixels<TPixel>(BufferedReadStream stream, Buffer2D<TPixel> pixels)
where TPixel : unmanaged, IPixel<TPixel> 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 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) private void ProcessUpscaling<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel> 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; float factor = maxAllocationValue / this.maxPixelValue;
image.Mutate(x => x.Brightness(factor)); 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> /// <summary>
/// Decoder for generating an image out of a png encoded stream. /// Decoder for generating an image out of a png encoded stream.
/// </summary> /// </summary>
public sealed class PngDecoder : IImageDecoder, IPngDecoderOptions, IImageInfoDetector public sealed class PngDecoder : IImageDecoder
{ {
/// <inheritdoc/> /// <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/> /// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken) Image<TPixel> IImageDecoder.Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{ {
PngDecoderCore decoder = new(configuration, this); Guard.NotNull(options, nameof(options));
return decoder.Decode<TPixel>(configuration, stream, cancellationToken); 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 /> /// <inheritdoc/>
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{ {
PngDecoderCore decoder = new(configuration, true); Guard.NotNull(options, nameof(options));
IImageInfo info = decoder.Identify(configuration, stream, cancellationToken); Guard.NotNull(stream, nameof(stream));
PngDecoderCore decoder = new(options, true);
IImageInfo info = decoder.Identify(options.Configuration, stream, cancellationToken);
stream.Position = 0; stream.Position = 0;
PngMetadata meta = info.Metadata.GetPngMetadata(); PngMetadata meta = info.Metadata.GetPngMetadata();
PngColorType color = meta.ColorType.GetValueOrDefault(); PngColorType color = meta.ColorType.GetValueOrDefault();
PngBitDepth bits = meta.BitDepth.GetValueOrDefault(); PngBitDepth bits = meta.BitDepth.GetValueOrDefault();
var imageDecoder = (IImageDecoder)this;
switch (color) switch (color)
{ {
case PngColorType.Grayscale: case PngColorType.Grayscale:
if (bits == PngBitDepth.Bit16) if (bits == PngBitDepth.Bit16)
{ {
return !meta.HasTransparency return !meta.HasTransparency
? this.Decode<L16>(configuration, stream, cancellationToken) ? imageDecoder.Decode<L16>(options, stream, cancellationToken)
: this.Decode<La32>(configuration, stream, cancellationToken); : imageDecoder.Decode<La32>(options, stream, cancellationToken);
} }
return !meta.HasTransparency return !meta.HasTransparency
? this.Decode<L8>(configuration, stream, cancellationToken) ? imageDecoder.Decode<L8>(options, stream, cancellationToken)
: this.Decode<La16>(configuration, stream, cancellationToken); : imageDecoder.Decode<La16>(options, stream, cancellationToken);
case PngColorType.Rgb: case PngColorType.Rgb:
if (bits == PngBitDepth.Bit16) if (bits == PngBitDepth.Bit16)
{ {
return !meta.HasTransparency return !meta.HasTransparency
? this.Decode<Rgb48>(configuration, stream, cancellationToken) ? imageDecoder.Decode<Rgb48>(options, stream, cancellationToken)
: this.Decode<Rgba64>(configuration, stream, cancellationToken); : imageDecoder.Decode<Rgba64>(options, stream, cancellationToken);
} }
return !meta.HasTransparency return !meta.HasTransparency
? this.Decode<Rgb24>(configuration, stream, cancellationToken) ? imageDecoder.Decode<Rgb24>(options, stream, cancellationToken)
: this.Decode<Rgba32>(configuration, stream, cancellationToken); : imageDecoder.Decode<Rgba32>(options, stream, cancellationToken);
case PngColorType.Palette: case PngColorType.Palette:
return this.Decode<Rgba32>(configuration, stream, cancellationToken); return imageDecoder.Decode<Rgba32>(options, stream, cancellationToken);
case PngColorType.GrayscaleWithAlpha: case PngColorType.GrayscaleWithAlpha:
return (bits == PngBitDepth.Bit16) return (bits == PngBitDepth.Bit16)
? this.Decode<La32>(configuration, stream, cancellationToken) ? imageDecoder.Decode<La32>(options, stream, cancellationToken)
: this.Decode<La16>(configuration, stream, cancellationToken); : imageDecoder.Decode<La16>(options, stream, cancellationToken);
case PngColorType.RgbWithAlpha: case PngColorType.RgbWithAlpha:
return (bits == PngBitDepth.Bit16) return (bits == PngBitDepth.Bit16)
? this.Decode<Rgba64>(configuration, stream, cancellationToken) ? imageDecoder.Decode<Rgba64>(options, stream, cancellationToken)
: this.Decode<Rgba32>(configuration, stream, cancellationToken); : imageDecoder.Decode<Rgba32>(options, stream, cancellationToken);
default: 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);
}
} }
} }

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

@ -4,7 +4,6 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@ -35,10 +34,15 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary> /// </summary>
private readonly byte[] buffer = new byte[4]; private readonly byte[] buffer = new byte[4];
/// <summary>
/// The general decoder options.
/// </summary>
private readonly Configuration configuration;
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary> /// </summary>
private readonly bool ignoreMetadata; private readonly bool skipMetadata;
/// <summary> /// <summary>
/// Gets or sets a value indicating whether to read the IHDR and tRNS chunks only. /// Gets or sets a value indicating whether to read the IHDR and tRNS chunks only.
@ -118,29 +122,28 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PngDecoderCore"/> class. /// Initializes a new instance of the <see cref="PngDecoderCore"/> class.
/// </summary> /// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="options">The decoder options.</param> /// <param name="options">The decoder options.</param>
public PngDecoderCore(Configuration configuration, IPngDecoderOptions options) public PngDecoderCore(DecoderOptions options)
{ {
this.Configuration = configuration ?? Configuration.Default; this.Options = options;
this.memoryAllocator = this.Configuration.MemoryAllocator; this.configuration = options.Configuration;
this.ignoreMetadata = options.IgnoreMetadata; 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.Options = options;
this.memoryAllocator = this.Configuration.MemoryAllocator;
this.colorMetadataOnly = colorMetadataOnly; this.colorMetadataOnly = colorMetadataOnly;
this.ignoreMetadata = true; this.skipMetadata = true;
this.configuration = options.Configuration;
this.memoryAllocator = this.configuration.MemoryAllocator;
} }
/// <inheritdoc/> /// <inheritdoc/>
public Configuration Configuration { get; } public DecoderOptions Options { get; }
/// <summary> /// <inheritdoc/>
/// Gets the dimensions of the image.
/// </summary>
public Size Dimensions => new(this.header.Width, this.header.Height); public Size Dimensions => new(this.header.Width, this.header.Height);
/// <inheritdoc/> /// <inheritdoc/>
@ -199,7 +202,7 @@ namespace SixLabors.ImageSharp.Formats.Png
this.ReadInternationalTextChunk(metadata, chunk.Data.GetSpan()); this.ReadInternationalTextChunk(metadata, chunk.Data.GetSpan());
break; break;
case PngChunkType.Exif: case PngChunkType.Exif:
if (!this.ignoreMetadata) if (!this.skipMetadata)
{ {
byte[] exifData = new byte[chunk.Length]; byte[] exifData = new byte[chunk.Length];
chunk.Data.GetSpan().CopyTo(exifData); chunk.Data.GetSpan().CopyTo(exifData);
@ -336,7 +339,7 @@ namespace SixLabors.ImageSharp.Formats.Png
break; break;
} }
if (!this.ignoreMetadata) if (!this.skipMetadata)
{ {
byte[] exifData = new byte[chunk.Length]; byte[] exifData = new byte[chunk.Length];
chunk.Data.GetSpan().CopyTo(exifData); chunk.Data.GetSpan().CopyTo(exifData);
@ -380,8 +383,8 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary> /// <summary>
/// Reads the least significant bits from the byte pair with the others set to 0. /// Reads the least significant bits from the byte pair with the others set to 0.
/// </summary> /// </summary>
/// <param name="buffer">The source buffer</param> /// <param name="buffer">The source buffer.</param>
/// <param name="offset">THe offset</param> /// <param name="offset">THe offset.</param>
/// <returns>The <see cref="int"/></returns> /// <returns>The <see cref="int"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte ReadByteLittleEndian(ReadOnlySpan<byte> buffer, int offset) private static byte ReadByteLittleEndian(ReadOnlySpan<byte> buffer, int offset)
@ -392,7 +395,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// specified number of bits. /// specified number of bits.
/// </summary> /// </summary>
/// <param name="source">The bytes to convert from. Cannot be empty.</param> /// <param name="source">The bytes to convert from. Cannot be empty.</param>
/// <param name="bytesPerScanline">The number of bytes per scanline</param> /// <param name="bytesPerScanline">The number of bytes per scanline.</param>
/// <param name="bits">The number of bits per value.</param> /// <param name="bits">The number of bits per value.</param>
/// <param name="buffer">The new array.</param> /// <param name="buffer">The new array.</param>
/// <returns>The resulting <see cref="ReadOnlySpan{Byte}"/> array.</returns> /// <returns>The resulting <see cref="ReadOnlySpan{Byte}"/> array.</returns>
@ -469,7 +472,7 @@ namespace SixLabors.ImageSharp.Formats.Png
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
image = Image.CreateUninitialized<TPixel>( image = Image.CreateUninitialized<TPixel>(
this.Configuration, this.configuration,
this.header.Width, this.header.Width,
this.header.Height, this.header.Height,
metadata); metadata);
@ -485,7 +488,7 @@ namespace SixLabors.ImageSharp.Formats.Png
this.previousScanline?.Dispose(); this.previousScanline?.Dispose();
this.scanline?.Dispose(); this.scanline?.Dispose();
this.previousScanline = this.memoryAllocator.Allocate<byte>(this.bytesPerScanline, AllocationOptions.Clean); 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> /// <summary>
@ -798,7 +801,7 @@ namespace SixLabors.ImageSharp.Formats.Png
case PngColorType.Rgb: case PngColorType.Rgb:
PngScanlineProcessor.ProcessRgbScanline( PngScanlineProcessor.ProcessRgbScanline(
this.Configuration, this.configuration,
this.header, this.header,
scanlineSpan, scanlineSpan,
rowSpan, rowSpan,
@ -812,7 +815,7 @@ namespace SixLabors.ImageSharp.Formats.Png
case PngColorType.RgbWithAlpha: case PngColorType.RgbWithAlpha:
PngScanlineProcessor.ProcessRgbaScanline( PngScanlineProcessor.ProcessRgbaScanline(
this.Configuration, this.configuration,
this.header, this.header,
scanlineSpan, scanlineSpan,
rowSpan, rowSpan,
@ -973,6 +976,10 @@ namespace SixLabors.ImageSharp.Formats.Png
pngMetadata.HasTransparency = true; pngMetadata.HasTransparency = true;
} }
} }
else if (this.pngColorType == PngColorType.Palette && alpha.Length > 0)
{
pngMetadata.HasTransparency = true;
}
} }
/// <summary> /// <summary>
@ -1001,7 +1008,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="data">The <see cref="T:Span"/> containing the data.</param> /// <param name="data">The <see cref="T:Span"/> containing the data.</param>
private void ReadTextChunk(ImageMetadata baseMetadata, PngMetadata metadata, ReadOnlySpan<byte> data) private void ReadTextChunk(ImageMetadata baseMetadata, PngMetadata metadata, ReadOnlySpan<byte> data)
{ {
if (this.ignoreMetadata) if (this.skipMetadata)
{ {
return; return;
} }
@ -1036,7 +1043,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="data">The <see cref="T:Span"/> containing the data.</param> /// <param name="data">The <see cref="T:Span"/> containing the data.</param>
private void ReadCompressedTextChunk(ImageMetadata baseMetadata, PngMetadata metadata, ReadOnlySpan<byte> data) private void ReadCompressedTextChunk(ImageMetadata baseMetadata, PngMetadata metadata, ReadOnlySpan<byte> data)
{ {
if (this.ignoreMetadata) if (this.skipMetadata)
{ {
return; return;
} }
@ -1222,10 +1229,10 @@ namespace SixLabors.ImageSharp.Formats.Png
{ {
fixed (byte* compressedDataBase = compressedData) 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 memoryStreamOutput = new MemoryStream(compressedData.Length))
using (var memoryStreamInput = new UnmanagedMemoryStream(compressedDataBase, 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)) using (var inflateStream = new ZlibInflateStream(bufferedStream))
{ {
Span<byte> destUncompressedData = destBuffer.GetSpan(); Span<byte> destUncompressedData = destBuffer.GetSpan();
@ -1324,7 +1331,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="data">The <see cref="T:Span"/> containing the data.</param> /// <param name="data">The <see cref="T:Span"/> containing the data.</param>
private void ReadInternationalTextChunk(ImageMetadata metadata, ReadOnlySpan<byte> data) private void ReadInternationalTextChunk(ImageMetadata metadata, ReadOnlySpan<byte> data)
{ {
if (this.ignoreMetadata) if (this.skipMetadata)
{ {
return; return;
} }
@ -1563,7 +1570,7 @@ namespace SixLabors.ImageSharp.Formats.Png
private IMemoryOwner<byte> ReadChunkData(int length) private IMemoryOwner<byte> ReadChunkData(int length)
{ {
// We rent the buffer here to return it afterwards in Decode() // 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); 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> /// <summary>
/// Image decoder for Truevision TGA images. /// Image decoder for Truevision TGA images.
/// </summary> /// </summary>
public sealed class TgaDecoder : IImageDecoder, ITgaDecoderOptions, IImageInfoDetector public sealed class TgaDecoder : IImageDecoder
{ {
/// <inheritdoc/> /// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken) IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{ {
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
var decoder = new TgaDecoderCore(configuration, this); return new TgaDecoderCore(options).Identify(options.Configuration, stream, cancellationToken);
return decoder.Decode<TPixel>(configuration, stream, cancellationToken);
} }
/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>(configuration, stream, cancellationToken);
/// <inheritdoc/> /// <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)); 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> /// </summary>
private readonly byte[] scratchBuffer = new byte[4]; private readonly byte[] scratchBuffer = new byte[4];
/// <summary>
/// General configuration options.
/// </summary>
private readonly Configuration configuration;
/// <summary> /// <summary>
/// The metadata. /// The metadata.
/// </summary> /// </summary>
@ -47,11 +52,6 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// </summary> /// </summary>
private BufferedReadStream currentStream; private BufferedReadStream currentStream;
/// <summary>
/// The bitmap decoder options.
/// </summary>
private readonly ITgaDecoderOptions options;
/// <summary> /// <summary>
/// Indicates whether there is a alpha channel present. /// Indicates whether there is a alpha channel present.
/// </summary> /// </summary>
@ -60,21 +60,18 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="TgaDecoderCore"/> class. /// Initializes a new instance of the <see cref="TgaDecoderCore"/> class.
/// </summary> /// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="options">The options.</param> /// <param name="options">The options.</param>
public TgaDecoderCore(Configuration configuration, ITgaDecoderOptions options) public TgaDecoderCore(DecoderOptions options)
{ {
this.Configuration = configuration; this.Options = options;
this.memoryAllocator = configuration.MemoryAllocator; this.configuration = options.Configuration;
this.options = options; this.memoryAllocator = this.configuration.MemoryAllocator;
} }
/// <inheritdoc /> /// <inheritdoc />
public Configuration Configuration { get; } public DecoderOptions Options { get; }
/// <summary> /// <inheritdoc />
/// Gets the dimensions of the image.
/// </summary>
public Size Dimensions => new(this.fileHeader.Width, this.fileHeader.Height); public Size Dimensions => new(this.fileHeader.Width, this.fileHeader.Height);
/// <inheritdoc /> /// <inheritdoc />
@ -97,7 +94,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
throw new UnknownImageFormatException("Width or height cannot be 0"); 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(); Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer();
if (this.fileHeader.ColorMapType == 1) if (this.fileHeader.ColorMapType == 1)
@ -463,11 +460,11 @@ namespace SixLabors.ImageSharp.Formats.Tga
if (this.fileHeader.ImageType == TgaImageType.BlackAndWhite) 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 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); 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)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -709,7 +706,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
} }
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y); 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)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -738,7 +735,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
} }
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y); 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)] [MethodImpl(MethodImplOptions.AggressiveInlining)]

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

@ -3,6 +3,7 @@
using System; using System;
using System.IO.Compression; using System.IO.Compression;
using System.Threading;
using SixLabors.ImageSharp.Compression.Zlib; using SixLabors.ImageSharp.Compression.Zlib;
using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
@ -40,7 +41,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
} }
/// <inheritdoc/> /// <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; long pos = stream.Position;
using (var deframeStream = new ZlibInflateStream( 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> /// </summary>
internal sealed class JpegTiffCompression : TiffBaseDecompressor internal sealed class JpegTiffCompression : TiffBaseDecompressor
{ {
private readonly Configuration configuration; private readonly JpegDecoderOptions options;
private readonly byte[] jpegTables; private readonly byte[] jpegTables;
@ -27,14 +27,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="JpegTiffCompression"/> class. /// Initializes a new instance of the <see cref="JpegTiffCompression"/> class.
/// </summary> /// </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="memoryAllocator">The memoryAllocator to use for buffer allocations.</param>
/// <param name="width">The image width.</param> /// <param name="width">The image width.</param>
/// <param name="bitsPerPixel">The bits per pixel.</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="jpegTables">The JPEG tables containing the quantization and/or Huffman tables.</param>
/// <param name="photometricInterpretation">The photometric interpretation.</param> /// <param name="photometricInterpretation">The photometric interpretation.</param>
public JpegTiffCompression( public JpegTiffCompression(
Configuration configuration, JpegDecoderOptions options,
MemoryAllocator memoryAllocator, MemoryAllocator memoryAllocator,
int width, int width,
int bitsPerPixel, int bitsPerPixel,
@ -42,31 +42,29 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
TiffPhotometricInterpretation photometricInterpretation) TiffPhotometricInterpretation photometricInterpretation)
: base(memoryAllocator, width, bitsPerPixel) : base(memoryAllocator, width, bitsPerPixel)
{ {
this.configuration = configuration; this.options = options;
this.jpegTables = jpegTables; this.jpegTables = jpegTables;
this.photometricInterpretation = photometricInterpretation; this.photometricInterpretation = photometricInterpretation;
} }
/// <inheritdoc/> /// <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) 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) switch (this.photometricInterpretation)
{ {
case TiffPhotometricInterpretation.BlackIsZero: case TiffPhotometricInterpretation.BlackIsZero:
case TiffPhotometricInterpretation.WhiteIsZero: case TiffPhotometricInterpretation.WhiteIsZero:
{ {
using SpectralConverter<L8> spectralConverterGray = using SpectralConverter<L8> spectralConverterGray = new GrayJpegSpectralConverter<L8>(configuration);
new GrayJpegSpectralConverter<L8>(this.configuration); var scanDecoderGray = new HuffmanScanDecoder(stream, spectralConverterGray, cancellationToken);
var scanDecoderGray = new HuffmanScanDecoder(stream, spectralConverterGray, CancellationToken.None);
jpegDecoder.LoadTables(this.jpegTables, scanDecoderGray); 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);
using Buffer2D<L8> decompressedBuffer = spectralConverterGray.GetPixelBuffer(CancellationToken.None);
CopyImageBytesToBuffer(buffer, decompressedBuffer); CopyImageBytesToBuffer(buffer, decompressedBuffer);
break; break;
} }
@ -75,13 +73,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
case TiffPhotometricInterpretation.Rgb: case TiffPhotometricInterpretation.Rgb:
{ {
using SpectralConverter<Rgb24> spectralConverter = using SpectralConverter<Rgb24> spectralConverter =
new TiffJpegSpectralConverter<Rgb24>(this.configuration, this.photometricInterpretation); new TiffJpegSpectralConverter<Rgb24>(configuration, this.photometricInterpretation);
var scanDecoder = new HuffmanScanDecoder(stream, spectralConverter, CancellationToken.None); var scanDecoder = new HuffmanScanDecoder(stream, spectralConverter, cancellationToken);
jpegDecoder.LoadTables(this.jpegTables, scanDecoder); 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);
using Buffer2D<Rgb24> decompressedBuffer = spectralConverter.GetPixelBuffer(CancellationToken.None);
CopyImageBytesToBuffer(buffer, decompressedBuffer); CopyImageBytesToBuffer(buffer, decompressedBuffer);
break; break;
} }

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

@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using System; using System;
using System.Threading;
using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
@ -35,7 +36,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
} }
/// <inheritdoc/> /// <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); var decoder = new TiffLzwDecoder(stream);
decoder.DecodePixels(buffer); decoder.DecodePixels(buffer);

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

@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using System; using System;
using System.Threading;
using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -40,7 +41,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
private TiffFillOrder FillOrder { get; } private TiffFillOrder FillOrder { get; }
/// <inheritdoc/> /// <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); 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. // Licensed under the Six Labors Split License.
using System; using System;
using System.Threading;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -24,7 +25,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
} }
/// <inheritdoc/> /// <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)); => _ = stream.Read(buffer, 0, Math.Min(buffer.Length, byteCount));
/// <inheritdoc/> /// <inheritdoc/>

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

@ -3,6 +3,7 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.Threading;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -27,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
} }
/// <inheritdoc/> /// <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) if (this.compressedDataMemory == null)
{ {

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

@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using System; using System;
using System.Threading;
using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -53,7 +54,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
private TiffFillOrder FillOrder { get; } private TiffFillOrder FillOrder { get; }
/// <inheritdoc/> /// <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)) if (this.faxCompressionOptions.HasFlag(FaxCompressionOptions.TwoDimensionalCoding))
{ {

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

@ -4,6 +4,7 @@
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading;
using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -49,7 +50,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
private TiffFillOrder FillOrder { get; } private TiffFillOrder FillOrder { get; }
/// <inheritdoc/> /// <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; int height = stripHeight;
buffer.Clear(); 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); return JpegColorConverterBase.GetConverter(colorSpace, frame.Precision);
} }
/// <remarks> /// <summary>
/// This converter must be used only for RGB and YCbCr color spaces for performance reasons. /// 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. /// For grayscale images <see cref="GrayJpegSpectralConverter{TPixel}"/> must be used.
/// </remarks> /// </summary>
private static JpegColorSpace GetJpegColorSpaceFromPhotometricInterpretation(TiffPhotometricInterpretation interpretation) private static JpegColorSpace GetJpegColorSpaceFromPhotometricInterpretation(TiffPhotometricInterpretation interpretation)
=> interpretation switch => interpretation switch
{ {

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

@ -3,6 +3,7 @@
using System; using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading;
using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Formats.Webp;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
@ -16,22 +17,24 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// </summary> /// </summary>
internal class WebpTiffCompression : TiffBaseDecompressor internal class WebpTiffCompression : TiffBaseDecompressor
{ {
private readonly DecoderOptions options;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="WebpTiffCompression"/> class. /// Initializes a new instance of the <see cref="WebpTiffCompression"/> class.
/// </summary> /// </summary>
/// <param name="options">The general decoder options.</param>
/// <param name="memoryAllocator">The memory allocator.</param> /// <param name="memoryAllocator">The memory allocator.</param>
/// <param name="width">The width of the image.</param> /// <param name="width">The width of the image.</param>
/// <param name="bitsPerPixel">The bits per pixel.</param> /// <param name="bitsPerPixel">The bits per pixel.</param>
/// <param name="predictor">The predictor.</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) : base(memoryAllocator, width, bitsPerPixel, predictor)
{ => this.options = options;
}
/// <inheritdoc/> /// <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); CopyImageBytesToBuffer(buffer, image.Frames.RootFrame.PixelBuffer);
} }

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

@ -3,6 +3,7 @@
using System; using System;
using System.IO; using System.IO;
using System.Threading;
using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory; 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="stripByteCount">The number of bytes to read from the input stream.</param>
/// <param name="stripHeight">The height of the strip.</param> /// <param name="stripHeight">The height of the strip.</param>
/// <param name="buffer">The output buffer for uncompressed data.</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(stripOffset, (ulong)long.MaxValue, nameof(stripOffset));
DebugGuard.MustBeLessThanOrEqualTo(stripByteCount, (ulong)long.MaxValue, nameof(stripByteCount)); DebugGuard.MustBeLessThanOrEqualTo(stripByteCount, (ulong)long.MaxValue, nameof(stripByteCount));
stream.Seek((long)stripOffset, SeekOrigin.Begin); 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) 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="byteCount">The number of bytes to read from the input stream.</param>
/// <param name="stripHeight">The height of the strip.</param> /// <param name="stripHeight">The height of the strip.</param>
/// <param name="buffer">The output buffer for uncompressed data.</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 internal static class TiffDecompressorsFactory
{ {
public static TiffBaseDecompressor Create( public static TiffBaseDecompressor Create(
Configuration configuration, DecoderOptions options,
TiffDecoderCompressionType method, TiffDecoderCompressionType method,
MemoryAllocator allocator, MemoryAllocator allocator,
TiffPhotometricInterpretation photometricInterpretation, TiffPhotometricInterpretation photometricInterpretation,
@ -58,11 +58,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
case TiffDecoderCompressionType.Jpeg: case TiffDecoderCompressionType.Jpeg:
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); 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: case TiffDecoderCompressionType.Webp:
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); 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: default:
throw TiffThrowHelper.NotSupportedDecompressor(nameof(method)); 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.IO;
using System.Threading; using System.Threading;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff namespace SixLabors.ImageSharp.Formats.Tiff
@ -11,39 +10,33 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <summary> /// <summary>
/// Image decoder for generating an image out of a TIFF stream. /// Image decoder for generating an image out of a TIFF stream.
/// </summary> /// </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/> /// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken) IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{ {
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
var decoder = new TiffDecoderCore(configuration, this); return new TiffDecoderCore(options).Identify(options.Configuration, stream, cancellationToken);
return decoder.Decode<TPixel>(configuration, stream, cancellationToken);
} }
/// <inheritdoc/> /// <inheritdoc/>
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) Image<TPixel> IImageDecoder.Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>(configuration, stream, cancellationToken);
/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{ {
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
var decoder = new TiffDecoderCore(configuration, this); TiffDecoderCore decoder = new(options);
return decoder.Identify(configuration, stream, cancellationToken); 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> /// </summary>
internal class TiffDecoderCore : IImageDecoderInternals internal class TiffDecoderCore : IImageDecoderInternals
{ {
/// <summary>
/// General configuration options.
/// </summary>
private readonly Configuration configuration;
/// <summary> /// <summary>
/// Used for allocating memory during processing operations. /// Used for allocating memory during processing operations.
/// </summary> /// </summary>
private readonly MemoryAllocator memoryAllocator; private readonly MemoryAllocator memoryAllocator;
/// <summary> /// <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> /// </summary>
private readonly bool ignoreMetadata; private readonly bool skipMetadata;
/// <summary> /// <summary>
/// Gets the decoding mode for multi-frame images /// The maximum number of frames to decode. Inclusive.
/// </summary> /// </summary>
private readonly FrameDecodingMode decodingMode; private readonly uint maxFrames;
/// <summary> /// <summary>
/// The stream to decode from. /// The stream to decode from.
@ -55,16 +60,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="TiffDecoderCore" /> class. /// Initializes a new instance of the <see cref="TiffDecoderCore" /> class.
/// </summary> /// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="options">The decoder options.</param> /// <param name="options">The decoder options.</param>
public TiffDecoderCore(Configuration configuration, ITiffDecoderOptions options) public TiffDecoderCore(DecoderOptions options)
{ {
options ??= new TiffDecoder(); this.Options = options;
this.configuration = options.Configuration;
this.Configuration = configuration ?? Configuration.Default; this.skipMetadata = options.SkipMetadata;
this.ignoreMetadata = options.IgnoreMetadata; this.maxFrames = options.MaxFrames;
this.decodingMode = options.DecodingMode; this.memoryAllocator = this.configuration.MemoryAllocator;
this.memoryAllocator = this.Configuration.MemoryAllocator;
} }
/// <summary> /// <summary>
@ -148,7 +151,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
public TiffPredictor Predictor { get; set; } public TiffPredictor Predictor { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
public Configuration Configuration { get; } public DecoderOptions Options { get; }
/// <inheritdoc/> /// <inheritdoc/>
public Size Dimensions { get; private set; } public Size Dimensions { get; private set; }
@ -161,25 +164,26 @@ namespace SixLabors.ImageSharp.Formats.Tiff
try try
{ {
this.inputStream = stream; this.inputStream = stream;
var reader = new DirectoryReader(stream, this.Configuration.MemoryAllocator); var reader = new DirectoryReader(stream, this.configuration.MemoryAllocator);
IEnumerable<ExifProfile> directories = reader.Read(); IEnumerable<ExifProfile> directories = reader.Read();
this.byteOrder = reader.ByteOrder; this.byteOrder = reader.ByteOrder;
this.isBigTiff = reader.IsBigTiff; this.isBigTiff = reader.IsBigTiff;
uint frameCount = 0;
foreach (ExifProfile ifd in directories) foreach (ExifProfile ifd in directories)
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
ImageFrame<TPixel> frame = this.DecodeFrame<TPixel>(ifd, cancellationToken); ImageFrame<TPixel> frame = this.DecodeFrame<TPixel>(ifd, cancellationToken);
frames.Add(frame); frames.Add(frame);
if (this.decodingMode is FrameDecodingMode.First) if (++frameCount == this.maxFrames)
{ {
break; 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. // TODO: Tiff frames can have different sizes.
ImageFrame<TPixel> root = frames[0]; 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 catch
{ {
@ -209,7 +213,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{ {
this.inputStream = stream; this.inputStream = stream;
var reader = new DirectoryReader(stream, this.Configuration.MemoryAllocator); var reader = new DirectoryReader(stream, this.configuration.MemoryAllocator);
IEnumerable<ExifProfile> directories = reader.Read(); IEnumerable<ExifProfile> directories = reader.Read();
ExifProfile rootFrameExifProfile = directories.First(); ExifProfile rootFrameExifProfile = directories.First();
@ -233,7 +237,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
var imageFrameMetaData = new ImageFrameMetadata(); var imageFrameMetaData = new ImageFrameMetadata();
if (!this.ignoreMetadata) if (!this.skipMetadata)
{ {
imageFrameMetaData.ExifProfile = tags; imageFrameMetaData.ExifProfile = tags;
} }
@ -245,7 +249,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
int width = GetImageWidth(tags); int width = GetImageWidth(tags);
int height = GetImageHeight(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; 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( using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create(
this.Configuration, this.Options,
this.CompressionType, this.CompressionType,
this.memoryAllocator, this.memoryAllocator,
this.PhotometricInterpretation, this.PhotometricInterpretation,
@ -406,7 +410,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff
stripOffsets[stripIndex], stripOffsets[stripIndex],
stripByteCounts[stripIndex], stripByteCounts[stripIndex],
stripHeight, stripHeight,
stripBuffers[planeIndex].GetSpan()); stripBuffers[planeIndex].GetSpan(),
cancellationToken);
stripIndex += stripsPerPlane; stripIndex += stripsPerPlane;
} }
@ -449,7 +454,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
Buffer2D<TPixel> pixels = frame.PixelBuffer; Buffer2D<TPixel> pixels = frame.PixelBuffer;
using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create( using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create(
this.Configuration, this.Options,
this.CompressionType, this.CompressionType,
this.memoryAllocator, this.memoryAllocator,
this.PhotometricInterpretation, this.PhotometricInterpretation,
@ -463,7 +468,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
this.byteOrder); this.byteOrder);
TiffBaseColorDecoder<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.Create( TiffBaseColorDecoder<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.Create(
this.Configuration, this.configuration,
this.memoryAllocator, this.memoryAllocator,
this.ColorType, this.ColorType,
this.BitsPerSample, this.BitsPerSample,
@ -494,7 +499,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff
stripOffsets[stripIndex], stripOffsets[stripIndex],
stripByteCounts[stripIndex], stripByteCounts[stripIndex],
stripHeight, stripHeight,
stripBufferSpan); stripBufferSpan,
cancellationToken);
colorDecoder.Decode(stripBufferSpan, pixels, 0, top, frame.Width, stripHeight); 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> /// </summary>
private readonly Configuration configuration; private readonly Configuration configuration;
/// <summary>
/// The maximum number of frames to decode. Inclusive.
/// </summary>
private readonly uint maxFrames;
/// <summary> /// <summary>
/// The area to restore. /// The area to restore.
/// </summary> /// </summary>
@ -48,29 +53,24 @@ namespace SixLabors.ImageSharp.Formats.Webp
/// </summary> /// </summary>
private WebpMetadata webpMetadata; private WebpMetadata webpMetadata;
/// <summary>
/// The alpha data, if an ALPH chunk is present.
/// </summary>
private IMemoryOwner<byte> alphaData;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="WebpAnimationDecoder"/> class. /// Initializes a new instance of the <see cref="WebpAnimationDecoder"/> class.
/// </summary> /// </summary>
/// <param name="memoryAllocator">The memory allocator.</param> /// <param name="memoryAllocator">The memory allocator.</param>
/// <param name="configuration">The global configuration.</param> /// <param name="configuration">The global configuration.</param>
/// <param name="decodingMode">The frame decoding mode.</param> /// <param name="maxFrames">The maximum number of frames to decode. Inclusive.</param>
public WebpAnimationDecoder(MemoryAllocator memoryAllocator, Configuration configuration, FrameDecodingMode decodingMode) public WebpAnimationDecoder(MemoryAllocator memoryAllocator, Configuration configuration, uint maxFrames)
{ {
this.memoryAllocator = memoryAllocator; this.memoryAllocator = memoryAllocator;
this.configuration = configuration; 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> /// <summary>
/// Decodes the animated webp image from the specified stream. /// Decodes the animated webp image from the specified stream.
/// </summary> /// </summary>
@ -90,6 +90,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
this.webpMetadata = this.metadata.GetWebpMetadata(); this.webpMetadata = this.metadata.GetWebpMetadata();
this.webpMetadata.AnimationLoopCount = features.AnimationLoopCount; this.webpMetadata.AnimationLoopCount = features.AnimationLoopCount;
uint frameCount = 0;
int remainingBytes = (int)completeDataSize; int remainingBytes = (int)completeDataSize;
while (remainingBytes > 0) while (remainingBytes > 0)
{ {
@ -110,7 +111,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
break; break;
} }
if (stream.Position == stream.Length || this.DecodingMode is FrameDecodingMode.First) if (stream.Position == stream.Length || ++frameCount == this.maxFrames)
{ {
break; break;
} }
@ -224,14 +225,14 @@ namespace SixLabors.ImageSharp.Formats.Webp
/// <param name="stream">The stream to read from.</param> /// <param name="stream">The stream to read from.</param>
private byte ReadAlphaData(BufferedReadStream stream) private byte ReadAlphaData(BufferedReadStream stream)
{ {
this.AlphaData?.Dispose(); this.alphaData?.Dispose();
uint alphaChunkSize = WebpChunkParsingUtils.ReadChunkSize(stream, this.buffer); uint alphaChunkSize = WebpChunkParsingUtils.ReadChunkSize(stream, this.buffer);
int alphaDataSize = (int)(alphaChunkSize - 1); int alphaDataSize = (int)(alphaChunkSize - 1);
this.AlphaData = this.memoryAllocator.Allocate<byte>(alphaDataSize); this.alphaData = this.memoryAllocator.Allocate<byte>(alphaDataSize);
byte alphaChunkHeader = (byte)stream.ReadByte(); byte alphaChunkHeader = (byte)stream.ReadByte();
Span<byte> alphaData = this.AlphaData.GetSpan(); Span<byte> alphaData = this.alphaData.GetSpan();
stream.Read(alphaData, 0, alphaDataSize); stream.Read(alphaData, 0, alphaDataSize);
return alphaChunkHeader; return alphaChunkHeader;
@ -260,7 +261,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
else else
{ {
var lossyDecoder = new WebpLossyDecoder(webpInfo.Vp8BitReader, this.memoryAllocator, this.configuration); 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; return pixelBufferDecoded;
@ -381,6 +382,6 @@ namespace SixLabors.ImageSharp.Formats.Webp
} }
/// <inheritdoc/> /// <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.IO;
using System.Threading; using System.Threading;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Webp namespace SixLabors.ImageSharp.Formats.Webp
@ -12,49 +10,34 @@ namespace SixLabors.ImageSharp.Formats.Webp
/// <summary> /// <summary>
/// Image decoder for generating an image out of a webp stream. /// Image decoder for generating an image out of a webp stream.
/// </summary> /// </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/> /// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken) IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{ {
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
using var decoder = new WebpDecoderCore(configuration, this); using WebpDecoderCore decoder = new(options);
return decoder.Identify(options.Configuration, stream, cancellationToken);
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);
}
} }
/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>(configuration, stream, cancellationToken);
/// <inheritdoc/> /// <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)); 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;
using System.Buffers; using System.Buffers;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.IO;
using System.Threading; using System.Threading;
using SixLabors.ImageSharp.Formats.Webp.Lossless; using SixLabors.ImageSharp.Formats.Webp.Lossless;
using SixLabors.ImageSharp.Formats.Webp.Lossy; using SixLabors.ImageSharp.Formats.Webp.Lossy;
@ -29,65 +28,68 @@ namespace SixLabors.ImageSharp.Formats.Webp
private readonly byte[] buffer = new byte[4]; private readonly byte[] buffer = new byte[4];
/// <summary> /// <summary>
/// Used for allocating memory during the decoding operations. /// General configuration options.
/// </summary> /// </summary>
private readonly MemoryAllocator memoryAllocator; private readonly Configuration configuration;
/// <summary> /// <summary>
/// The stream to decode from. /// A value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary> /// </summary>
private BufferedReadStream currentStream; private readonly bool skipMetadata;
/// <summary> /// <summary>
/// The webp specific metadata. /// The maximum number of frames to decode. Inclusive.
/// </summary> /// </summary>
private WebpMetadata webpMetadata; private readonly uint maxFrames;
/// <summary> /// <summary>
/// Information about the webp image. /// Gets the <see cref="ImageMetadata"/> decoded by this decoder instance.
/// </summary> /// </summary>
private WebpImageInfo webImageInfo; private ImageMetadata metadata;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="WebpDecoderCore"/> class. /// Gets or sets the alpha data, if an ALPH chunk is present.
/// </summary> /// </summary>
/// <param name="configuration">The configuration.</param> private IMemoryOwner<byte> alphaData;
/// <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; }
/// <summary> /// <summary>
/// Gets the decoding mode for multi-frame images. /// Used for allocating memory during the decoding operations.
/// </summary> /// </summary>
public FrameDecodingMode DecodingMode { get; } private readonly MemoryAllocator memoryAllocator;
/// <summary> /// <summary>
/// Gets a value indicating whether the metadata should be ignored when the image is being decoded. /// The stream to decode from.
/// </summary> /// </summary>
public bool IgnoreMetadata { get; } private BufferedReadStream currentStream;
/// <summary> /// <summary>
/// Gets the <see cref="ImageMetadata"/> decoded by this decoder instance. /// The webp specific metadata.
/// </summary> /// </summary>
public ImageMetadata Metadata { get; private set; } private WebpMetadata webpMetadata;
/// <summary> /// <summary>
/// Gets the dimensions of the image. /// Information about the webp image.
/// </summary> /// </summary>
public Size Dimensions => new((int)this.webImageInfo.Width, (int)this.webImageInfo.Height); private WebpImageInfo webImageInfo;
/// <summary> /// <summary>
/// Gets or sets the alpha data, if an ALPH chunk is present. /// Initializes a new instance of the <see cref="WebpDecoderCore"/> class.
/// </summary> /// </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 /> /// <inheritdoc />
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken) public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
@ -96,7 +98,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
Image<TPixel> image = null; Image<TPixel> image = null;
try try
{ {
this.Metadata = new ImageMetadata(); this.metadata = new ImageMetadata();
this.currentStream = stream; this.currentStream = stream;
uint fileSize = this.ReadImageHeader(); uint fileSize = this.ReadImageHeader();
@ -105,7 +107,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
{ {
if (this.webImageInfo.Features is { Animation: true }) 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); 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"); 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(); Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer();
if (this.webImageInfo.IsLossless) 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); losslessDecoder.Decode(pixels, image.Width, image.Height);
} }
else else
{ {
var lossyDecoder = new WebpLossyDecoder(this.webImageInfo.Vp8BitReader, this.memoryAllocator, this.Configuration); var lossyDecoder = new WebpLossyDecoder(this.webImageInfo.Vp8BitReader, this.memoryAllocator, this.configuration);
lossyDecoder.Decode(pixels, image.Width, image.Height, this.webImageInfo, this.AlphaData); lossyDecoder.Decode(pixels, image.Width, image.Height, this.webImageInfo, this.alphaData);
} }
// There can be optional chunks after the image data, like EXIF and XMP. // There can be optional chunks after the image data, like EXIF and XMP.
@ -151,7 +153,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
this.ReadImageHeader(); this.ReadImageHeader();
using (this.webImageInfo = this.ReadVp8Info(true)) 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> /// <returns>Information about the webp image.</returns>
private WebpImageInfo ReadVp8Info(bool ignoreAlpha = false) private WebpImageInfo ReadVp8Info(bool ignoreAlpha = false)
{ {
this.Metadata = new ImageMetadata(); this.metadata = new ImageMetadata();
this.webpMetadata = this.Metadata.GetFormatMetadata(WebpFormat.Instance); this.webpMetadata = this.metadata.GetFormatMetadata(WebpFormat.Instance);
WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(this.currentStream, this.buffer); WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(this.currentStream, this.buffer);
@ -277,7 +279,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
/// <param name="features">The webp features.</param> /// <param name="features">The webp features.</param>
private void ParseOptionalChunks(WebpFeatures features) private void ParseOptionalChunks(WebpFeatures features)
{ {
if (this.IgnoreMetadata || (features.ExifProfile == false && features.XmpMetaData == false)) if (this.skipMetadata || (!features.ExifProfile && !features.XmpMetaData))
{ {
return; return;
} }
@ -287,11 +289,11 @@ namespace SixLabors.ImageSharp.Formats.Webp
{ {
// Read chunk header. // Read chunk header.
WebpChunkType chunkType = this.ReadChunkType(); WebpChunkType chunkType = this.ReadChunkType();
if (chunkType == WebpChunkType.Exif && this.Metadata.ExifProfile == null) if (chunkType == WebpChunkType.Exif && this.metadata.ExifProfile == null)
{ {
this.ReadExifProfile(); this.ReadExifProfile();
} }
else if (chunkType == WebpChunkType.Xmp && this.Metadata.XmpProfile == null) else if (chunkType == WebpChunkType.Xmp && this.metadata.XmpProfile == null)
{ {
this.ReadXmpProfile(); this.ReadXmpProfile();
} }
@ -310,7 +312,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
private void ReadExifProfile() private void ReadExifProfile()
{ {
uint exifChunkSize = this.ReadChunkSize(); uint exifChunkSize = this.ReadChunkSize();
if (this.IgnoreMetadata) if (this.skipMetadata)
{ {
this.currentStream.Skip((int)exifChunkSize); this.currentStream.Skip((int)exifChunkSize);
} }
@ -325,7 +327,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
} }
var profile = new ExifProfile(exifData); 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() private void ReadXmpProfile()
{ {
uint xmpChunkSize = this.ReadChunkSize(); uint xmpChunkSize = this.ReadChunkSize();
if (this.IgnoreMetadata) if (this.skipMetadata)
{ {
this.currentStream.Skip((int)xmpChunkSize); this.currentStream.Skip((int)xmpChunkSize);
} }
@ -350,7 +352,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
} }
var profile = new XmpProfile(xmpData); 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() private void ReadIccProfile()
{ {
uint iccpChunkSize = this.ReadChunkSize(); uint iccpChunkSize = this.ReadChunkSize();
if (this.IgnoreMetadata) if (this.skipMetadata)
{ {
this.currentStream.Skip((int)iccpChunkSize); this.currentStream.Skip((int)iccpChunkSize);
} }
@ -376,7 +378,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
var profile = new IccProfile(iccpData); var profile = new IccProfile(iccpData);
if (profile.CheckIsValid()) 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(); features.AlphaChunkHeader = (byte)this.currentStream.ReadByte();
int alphaDataSize = (int)(alphaChunkSize - 1); int alphaDataSize = (int)(alphaChunkSize - 1);
this.AlphaData = this.memoryAllocator.Allocate<byte>(alphaDataSize); this.alphaData = this.memoryAllocator.Allocate<byte>(alphaDataSize);
Span<byte> alphaData = this.AlphaData.GetSpan(); Span<byte> alphaData = this.alphaData.GetSpan();
int bytesRead = this.currentStream.Read(alphaData, 0, alphaDataSize); int bytesRead = this.currentStream.Read(alphaData, 0, alphaDataSize);
if (bytesRead != alphaDataSize) if (bytesRead != alphaDataSize)
{ {
@ -462,6 +464,6 @@ namespace SixLabors.ImageSharp.Formats.Webp
} }
/// <inheritdoc/> /// <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> /// <summary>
/// By reading the header on the provided stream this calculates the images format. /// By reading the header on the provided stream this calculates the images format.
/// </summary> /// </summary>
/// <param name="configuration">The general configuration.</param>
/// <param name="stream">The image stream to read the header from.</param> /// <param name="stream">The image stream to read the header from.</param>
/// <param name="config">The configuration.</param>
/// <returns>The mime type or null if none found.</returns> /// <returns>The mime type or null if none found.</returns>
private static IImageFormat InternalDetectFormat(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 // We take a minimum of the stream length vs the max header size and always check below
// to ensure that only formats that headers fit within the given buffer length are tested. // to ensure that only formats that headers fit within the given buffer length are tested.
int headerSize = (int)Math.Min(config.MaxHeaderSize, stream.Length); int headerSize = (int)Math.Min(configuration.MaxHeaderSize, stream.Length);
if (headerSize <= 0) if (headerSize <= 0)
{ {
return null; return null;
@ -80,7 +80,7 @@ namespace SixLabors.ImageSharp
// and does that data match the format specification? // and does that data match the format specification?
// Individual formats should still check since they are public. // Individual formats should still check since they are public.
IImageFormat format = null; IImageFormat format = null;
foreach (IImageFormatDetector formatDetector in config.ImageFormatsManager.FormatDetectors) foreach (IImageFormatDetector formatDetector in configuration.ImageFormatsManager.FormatDetectors)
{ {
if (formatDetector.HeaderSize <= headerSize) if (formatDetector.HeaderSize <= headerSize)
{ {
@ -98,73 +98,73 @@ namespace SixLabors.ImageSharp
/// <summary> /// <summary>
/// By reading the header on the provided stream this calculates the images format. /// By reading the header on the provided stream this calculates the images format.
/// </summary> /// </summary>
/// <param name="options">The general decoder options.</param>
/// <param name="stream">The image stream to read the header from.</param> /// <param name="stream">The image stream to read the header from.</param>
/// <param name="config">The configuration.</param>
/// <param name="format">The IImageFormat.</param> /// <param name="format">The IImageFormat.</param>
/// <returns>The image format or null if none found.</returns> /// <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 return format != null
? config.ImageFormatsManager.FindDecoder(format) ? options.Configuration.ImageFormatsManager.FindDecoder(format)
: null; : null;
} }
/// <summary> /// <summary>
/// Decodes the image stream to the current image. /// Decodes the image stream to the current image.
/// </summary> /// </summary>
/// <param name="options">The general decoder options.</param>
/// <param name="stream">The stream.</param> /// <param name="stream">The stream.</param>
/// <param name="config">the configuration.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns> /// <returns>
/// A new <see cref="Image{TPixel}"/>. /// A new <see cref="Image{TPixel}"/>.
/// </returns> /// </returns>
private static (Image<TPixel> Image, IImageFormat Format) Decode<TPixel>(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> 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) if (decoder is null)
{ {
return (null, 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); 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) if (decoder is null)
{ {
return (null, null); return (null, null);
} }
Image img = decoder.Decode(config, stream, cancellationToken); Image img = decoder.Decode(options, stream, cancellationToken);
return (img, format); return (img, format);
} }
/// <summary> /// <summary>
/// Reads the raw image information from the specified stream. /// Reads the raw image information from the specified stream.
/// </summary> /// </summary>
/// <param name="options">The general decoder options.</param>
/// <param name="stream">The stream.</param> /// <param name="stream">The stream.</param>
/// <param name="config">the configuration.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns> /// <returns>
/// The <see cref="IImageInfo"/> or null if a suitable info detector is not found. /// The <see cref="IImageInfo"/> or null if a suitable info detector is not found.
/// </returns> /// </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) if (decoder is not IImageInfoDetector detector)
{ {
return (null, null); return (null, null);
} }
IImageInfo info = detector?.Identify(config, stream, cancellationToken); IImageInfo info = detector?.Identify(options, stream, cancellationToken);
return (info, format); return (info, format);
} }
} }

451
src/ImageSharp/Image.FromBytes.cs

@ -9,47 +9,59 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp namespace SixLabors.ImageSharp
{ {
/// <content> /// <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> /// </content>
public abstract partial class Image public abstract partial class Image
{ {
/// <summary> /// <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> /// </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>
/// <returns>The format or null if none found.</returns> /// <returns>The format or null if none found.</returns>
public static IImageFormat DetectFormat(byte[] data) public static IImageFormat DetectFormat(ReadOnlySpan<byte> data)
=> DetectFormat(Configuration.Default, data); => DetectFormat(DecoderOptions.Default, data);
/// <summary> /// <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> /// </summary>
/// <param name="configuration">The configuration.</param> /// <param name="options">The general decoder options.</param>
/// <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 configuration is null.</exception> /// <exception cref="ArgumentNullException">The options are null.</exception>
/// <exception cref="ArgumentNullException">The data is null.</exception>
/// <returns>The mime type or null if none found.</returns> /// <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> /// <summary>
/// Reads the raw image information from the specified stream without fully decoding it. /// Reads the raw image information from the specified stream without fully decoding it.
/// </summary> /// </summary>
/// <param name="data">The byte 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="ArgumentNullException">The data is null.</exception>
/// <exception cref="NotSupportedException">The data is not readable.</exception> /// <exception cref="NotSupportedException">The data is not readable.</exception>
/// <returns> /// <returns>
/// The <see cref="IImageInfo"/> or null if suitable info detector not found. /// The <see cref="IImageInfo"/> or null if suitable info detector not found.
/// </returns> /// </returns>
public static IImageInfo Identify(byte[] data) => Identify(data, out IImageFormat _); public static IImageInfo Identify(ReadOnlySpan<byte> data) => Identify(data, out IImageFormat _);
/// <summary> /// <summary>
/// Reads the raw image information from the specified stream without fully decoding it. /// Reads the raw image information from the specified stream without fully decoding it.
@ -61,13 +73,14 @@ namespace SixLabors.ImageSharp
/// <returns> /// <returns>
/// The <see cref="IImageInfo"/> or null if suitable info detector not found. /// The <see cref="IImageInfo"/> or null if suitable info detector not found.
/// </returns> /// </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> /// <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> /// </summary>
/// <param name="configuration">The configuration.</param> /// <param name="options">The general decoder options.</param>
/// <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>
/// <param name="format">The format type of the decoded image.</param> /// <param name="format">The format type of the decoded image.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The data is null.</exception> /// <exception cref="ArgumentNullException">The data is null.</exception>
@ -75,185 +88,15 @@ namespace SixLabors.ImageSharp
/// <returns> /// <returns>
/// The <see cref="IImageInfo"/> or null if suitable info detector is not found. /// The <see cref="IImageInfo"/> or null if suitable info detector is not found.
/// </returns> /// </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)); fixed (byte* ptr = data)
using (var stream = new MemoryStream(data, 0, data.Length, false, true))
{ {
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> /// <summary>
/// Load a new instance of <see cref="Image{TPixel}"/> from the given encoded byte span. /// Load a new instance of <see cref="Image{TPixel}"/> from the given encoded byte span.
/// </summary> /// </summary>
@ -265,7 +108,7 @@ namespace SixLabors.ImageSharp
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(ReadOnlySpan<byte> data) public static Image<TPixel> Load<TPixel>(ReadOnlySpan<byte> data)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
=> Load<TPixel>(Configuration.Default, data); => Load<TPixel>(DecoderOptions.Default, data);
/// <summary> /// <summary>
/// Load a new instance of <see cref="Image{TPixel}"/> from the given encoded byte span. /// 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> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(ReadOnlySpan<byte> data, out IImageFormat format) public static Image<TPixel> Load<TPixel>(ReadOnlySpan<byte> data, out IImageFormat format)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
=> Load<TPixel>(Configuration.Default, data, out format); => Load<TPixel>(DecoderOptions.Default, data, out format);
/// <summary> /// <summary>
/// Load a new instance of <see cref="Image{TPixel}"/> from the given encoded byte span. /// Load a new instance of <see cref="Image{TPixel}"/> from the given encoded byte span.
/// </summary> /// </summary>
/// <param name="options">The general decoder options.</param>
/// <param name="data">The byte span containing encoded image data.</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> /// <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="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception> /// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static unsafe Image<TPixel> Load<TPixel>( public static unsafe Image<TPixel> Load<TPixel>(DecoderOptions options, ReadOnlySpan<byte> data)
Configuration configuration,
ReadOnlySpan<byte> data,
IImageDecoder decoder)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
fixed (byte* ptr = &data.GetPinnableReference()) fixed (byte* ptr = data)
{ {
using (var stream = new UnmanagedMemoryStream(ptr, data.Length)) using var stream = new UnmanagedMemoryStream(ptr, data.Length);
{ return Load<TPixel>(options, stream);
return Load<TPixel>(configuration, stream, decoder);
}
} }
} }
/// <summary> /// <summary>
/// Load a new instance of <see cref="Image{TPixel}"/> from the given encoded byte span. /// Load a new instance of <see cref="Image{TPixel}"/> from the given encoded byte span.
/// </summary> /// </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="data">The byte span containing image data.</param>
/// <param name="format">The <see cref="IImageFormat"/> of the decoded image.</param> /// <param name="format">The <see cref="IImageFormat"/> of the decoded image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The options are null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception> /// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static unsafe Image<TPixel> Load<TPixel>( public static unsafe Image<TPixel> Load<TPixel>(
Configuration configuration, DecoderOptions options,
ReadOnlySpan<byte> data, ReadOnlySpan<byte> data,
out IImageFormat format) out IImageFormat format)
where TPixel : unmanaged, IPixel<TPixel> 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, 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))
{ {
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> /// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <returns>The <see cref="Image"/>.</returns> /// <returns>The <see cref="Image"/>.</returns>
public static Image Load(ReadOnlySpan<byte> data) public static Image Load(ReadOnlySpan<byte> data)
=> Load(Configuration.Default, data); => Load(DecoderOptions.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);
/// <summary> /// <summary>
/// Load a new instance of <see cref="Image"/> from the given encoded byte array. /// 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> /// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <returns>The <see cref="Image"/>.</returns> /// <returns>The <see cref="Image"/>.</returns>
public static Image Load(ReadOnlySpan<byte> data, out IImageFormat format) public static Image Load(ReadOnlySpan<byte> data, out IImageFormat format)
=> Load(Configuration.Default, data, out format); => Load(DecoderOptions.Default, data, out format);
/// <summary> /// <summary>
/// Decodes a new instance of <see cref="Image"/> from the given encoded byte span. /// Decodes a new instance of <see cref="Image"/> from the given encoded byte span.
/// </summary> /// </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="data">The byte span containing image data.</param>
/// <returns>The <see cref="Image"/>.</returns> /// <returns>The <see cref="Image"/>.</returns>
public static Image Load(Configuration configuration, ReadOnlySpan<byte> data) public static Image Load(DecoderOptions options, ReadOnlySpan<byte> data)
=> Load(configuration, data, out _); => Load(options, data, out _);
/// <summary> /// <summary>
/// Load a new instance of <see cref="Image"/> from the given encoded byte span. /// Load a new instance of <see cref="Image"/> from the given encoded byte span.
/// </summary> /// </summary>
/// <param name="configuration">The Configuration.</param> /// <param name="options">The general decoder options.</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="data">The byte span containing image data.</param> /// <param name="data">The byte span containing image data.</param>
/// <param name="format">The <see cref="IImageFormat"/> of the decoded image.</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="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception> /// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <returns>The <see cref="Image"/>.</returns> /// <returns>The <see cref="Image"/>.</returns>
public static unsafe Image Load( public static unsafe Image Load(
Configuration configuration, DecoderOptions options,
ReadOnlySpan<byte> data, ReadOnlySpan<byte> data,
out IImageFormat format) out IImageFormat format)
{ {
fixed (byte* ptr = &data.GetPinnableReference()) fixed (byte* ptr = data)
{ {
using (var stream = new UnmanagedMemoryStream(ptr, data.Length)) using var stream = new UnmanagedMemoryStream(ptr, data.Length);
{ return Load(options, stream, out format);
return Load(configuration, 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> /// <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> /// <returns>The mime type or null if none found.</returns>
public static IImageFormat DetectFormat(string filePath) public static IImageFormat DetectFormat(string filePath)
=> DetectFormat(Configuration.Default, filePath); => DetectFormat(DecoderOptions.Default, filePath);
/// <summary> /// <summary>
/// By reading the header on the provided file this calculates the images mime type. /// By reading the header on the provided file this calculates the images mime type.
/// </summary> /// </summary>
/// <param name="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="filePath">The image file to open and to read the header from.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <returns>The mime type or null if none found.</returns> /// <returns>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)) using Stream file = options.Configuration.FileSystem.OpenRead(filePath);
{ return DetectFormat(options, file);
return DetectFormat(configuration, file);
}
} }
/// <summary> /// <summary>
@ -59,25 +57,23 @@ namespace SixLabors.ImageSharp
/// The <see cref="IImageInfo"/> or null if suitable info detector not found. /// The <see cref="IImageInfo"/> or null if suitable info detector not found.
/// </returns> /// </returns>
public static IImageInfo Identify(string filePath, out IImageFormat format) public static IImageInfo Identify(string filePath, out IImageFormat format)
=> Identify(Configuration.Default, filePath, out format); => Identify(DecoderOptions.Default, filePath, out format);
/// <summary> /// <summary>
/// Reads the raw image information from the specified stream without fully decoding it. /// Reads the raw image information from the specified stream without fully decoding it.
/// </summary> /// </summary>
/// <param name="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="filePath">The image file to open and to read the header from.</param>
/// <param name="format">The format type of the decoded image.</param> /// <param name="format">The format type of the decoded image.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <returns> /// <returns>
/// The <see cref="IImageInfo"/> or null if suitable info detector is not found. /// The <see cref="IImageInfo"/> or null if suitable info detector is not found.
/// </returns> /// </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)); Guard.NotNull(options, nameof(options));
using (Stream file = configuration.FileSystem.OpenRead(filePath)) using Stream file = options.Configuration.FileSystem.OpenRead(filePath);
{ return Identify(options, file, out format);
return Identify(configuration, file, out format);
}
} }
/// <summary> /// <summary>
@ -91,12 +87,12 @@ namespace SixLabors.ImageSharp
/// <see cref="IImageInfo"/> property set to null if suitable info detector is not found. /// <see cref="IImageInfo"/> property set to null if suitable info detector is not found.
/// </returns> /// </returns>
public static Task<IImageInfo> IdentifyAsync(string filePath, CancellationToken cancellationToken = default) public static Task<IImageInfo> IdentifyAsync(string filePath, CancellationToken cancellationToken = default)
=> IdentifyAsync(Configuration.Default, filePath, cancellationToken); => IdentifyAsync(DecoderOptions.Default, filePath, cancellationToken);
/// <summary> /// <summary>
/// Reads the raw image information from the specified stream without fully decoding it. /// Reads the raw image information from the specified stream without fully decoding it.
/// </summary> /// </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="filePath">The image file to open and to read the header from.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
@ -105,12 +101,12 @@ namespace SixLabors.ImageSharp
/// <see cref="IImageInfo"/> property set to null if suitable info detector is not found. /// <see cref="IImageInfo"/> property set to null if suitable info detector is not found.
/// </returns> /// </returns>
public static async Task<IImageInfo> IdentifyAsync( public static async Task<IImageInfo> IdentifyAsync(
Configuration configuration, DecoderOptions options,
string filePath, string filePath,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
(IImageInfo ImageInfo, IImageFormat Format) res = await IdentifyWithFormatAsync(configuration, filePath, cancellationToken) (IImageInfo ImageInfo, IImageFormat Format) res =
.ConfigureAwait(false); await IdentifyWithFormatAsync(options, filePath, cancellationToken).ConfigureAwait(false);
return res.ImageInfo; return res.ImageInfo;
} }
@ -127,12 +123,12 @@ namespace SixLabors.ImageSharp
public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync( public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync(
string filePath, string filePath,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
=> IdentifyWithFormatAsync(Configuration.Default, filePath, cancellationToken); => IdentifyWithFormatAsync(DecoderOptions.Default, filePath, cancellationToken);
/// <summary> /// <summary>
/// Reads the raw image information from the specified stream without fully decoding it. /// Reads the raw image information from the specified stream without fully decoding it.
/// </summary> /// </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="filePath">The image file to open and to read the header from.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
@ -141,13 +137,13 @@ namespace SixLabors.ImageSharp
/// <see cref="IImageInfo"/> property set to null if suitable info detector is not found. /// <see cref="IImageInfo"/> property set to null if suitable info detector is not found.
/// </returns> /// </returns>
public static async Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync( public static async Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync(
Configuration configuration, DecoderOptions options,
string filePath, string filePath,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(options, nameof(options));
using Stream stream = configuration.FileSystem.OpenRead(filePath); using Stream stream = options.Configuration.FileSystem.OpenRead(filePath);
return await IdentifyWithFormatAsync(configuration, stream, cancellationToken) return await IdentifyWithFormatAsync(options, stream, cancellationToken)
.ConfigureAwait(false); .ConfigureAwait(false);
} }
@ -160,7 +156,7 @@ namespace SixLabors.ImageSharp
/// </exception> /// </exception>
/// <returns>The <see cref="Image"/>.</returns> /// <returns>The <see cref="Image"/>.</returns>
public static Image Load(string path) public static Image Load(string path)
=> Load(Configuration.Default, path); => Load(DecoderOptions.Default, path);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given file. /// Create a new instance of the <see cref="Image"/> class from the given file.
@ -172,12 +168,12 @@ namespace SixLabors.ImageSharp
/// </exception> /// </exception>
/// <returns>A new <see cref="Image{Rgba32}"/>.</returns> /// <returns>A new <see cref="Image{Rgba32}"/>.</returns>
public static Image Load(string path, out IImageFormat format) public static Image Load(string path, out IImageFormat format)
=> Load(Configuration.Default, path, out format); => Load(DecoderOptions.Default, path, out format);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given file. /// Create a new instance of the <see cref="Image"/> class from the given file.
/// </summary> /// </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="path">The file path to the image.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The path 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="NotSupportedException">Image format is not supported.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>The <see cref="Image"/>.</returns> /// <returns>The <see cref="Image"/>.</returns>
public static Image Load(Configuration configuration, string path) public static Image Load(DecoderOptions options, string path)
=> Load(configuration, path, out _); => Load(options, path, out _);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given file. /// Create a new instance of the <see cref="Image"/> class from the given file.
/// </summary> /// </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="path">The file path to the image.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
@ -201,40 +197,16 @@ namespace SixLabors.ImageSharp
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
public static async Task<Image> LoadAsync( public static async Task<Image> LoadAsync(
Configuration configuration, DecoderOptions options,
string path, string path,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
using Stream stream = configuration.FileSystem.OpenRead(path); using Stream stream = options.Configuration.FileSystem.OpenRead(path);
(Image img, _) = await LoadWithFormatAsync(configuration, stream, cancellationToken) (Image img, _) = await LoadWithFormatAsync(options, stream, cancellationToken)
.ConfigureAwait(false); .ConfigureAwait(false);
return img; 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> /// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given file. /// Create a new instance of the <see cref="Image"/> class from the given file.
/// </summary> /// </summary>
@ -248,99 +220,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
public static Task<Image> LoadAsync(string path, CancellationToken cancellationToken = default) public static Task<Image> LoadAsync(string path, CancellationToken cancellationToken = default)
=> LoadAsync(Configuration.Default, path, cancellationToken); => LoadAsync(DecoderOptions.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);
}
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given file. /// 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> /// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
public static Task<Image<TPixel>> LoadAsync<TPixel>(string path, CancellationToken cancellationToken = default) public static Task<Image<TPixel>> LoadAsync<TPixel>(string path, CancellationToken cancellationToken = default)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
=> LoadAsync<TPixel>(Configuration.Default, path, cancellationToken); => LoadAsync<TPixel>(DecoderOptions.Default, path, cancellationToken);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given file. /// Create a new instance of the <see cref="Image"/> class from the given file.
/// </summary> /// </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="path">The file path to the image.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
@ -372,31 +252,19 @@ namespace SixLabors.ImageSharp
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
public static async Task<Image<TPixel>> LoadAsync<TPixel>( public static async Task<Image<TPixel>> LoadAsync<TPixel>(
Configuration configuration, DecoderOptions options,
string path, string path,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using Stream stream = configuration.FileSystem.OpenRead(path); Guard.NotNull(options, nameof(options));
(Image<TPixel> img, _) = await LoadWithFormatAsync<TPixel>(configuration, stream, cancellationToken)
.ConfigureAwait(false); using Stream stream = options.Configuration.FileSystem.OpenRead(path);
(Image<TPixel> img, _) =
await LoadWithFormatAsync<TPixel>(options, stream, cancellationToken).ConfigureAwait(false);
return img; 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> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file.
/// </summary> /// </summary>
@ -409,7 +277,7 @@ namespace SixLabors.ImageSharp
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(string path) public static Image<TPixel> Load<TPixel>(string path)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
=> Load<TPixel>(Configuration.Default, path); => Load<TPixel>(DecoderOptions.Default, path);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file. /// 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> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(string path, out IImageFormat format) public static Image<TPixel> Load<TPixel>(string path, out IImageFormat format)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
=> Load<TPixel>(Configuration.Default, path, out format); => Load<TPixel>(DecoderOptions.Default, path, out format);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file.
/// </summary> /// </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="path">The file path to the image.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The path 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> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <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> where TPixel : unmanaged, IPixel<TPixel>
{ {
Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(options, nameof(options));
Guard.NotNull(path, nameof(path)); Guard.NotNull(path, nameof(path));
using (Stream stream = configuration.FileSystem.OpenRead(path)) using Stream stream = options.Configuration.FileSystem.OpenRead(path);
{ return Load<TPixel>(options, stream);
return Load<TPixel>(configuration, stream);
}
} }
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file.
/// </summary> /// </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="path">The file path to the image.</param>
/// <param name="format">The mime type of the decoded image.</param> /// <param name="format">The mime type of the decoded image.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
@ -463,23 +329,21 @@ namespace SixLabors.ImageSharp
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <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> where TPixel : unmanaged, IPixel<TPixel>
{ {
Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(options, nameof(options));
Guard.NotNull(path, nameof(path)); Guard.NotNull(path, nameof(path));
using (Stream stream = configuration.FileSystem.OpenRead(path)) using Stream stream = options.Configuration.FileSystem.OpenRead(path);
{ return Load<TPixel>(options, stream, out format);
return Load<TPixel>(configuration, stream, out format);
}
} }
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given file. /// Create a new instance of the <see cref="Image"/> class from the given file.
/// The pixel type is selected by the decoder. /// The pixel type is selected by the decoder.
/// </summary> /// </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="path">The file path to the image.</param>
/// <param name="format">The mime type of the decoded image.</param> /// <param name="format">The mime type of the decoded image.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <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="NotSupportedException">Image format is not supported.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image Load(Configuration configuration, string path, out IImageFormat format) public static Image Load(DecoderOptions options, 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>
{ {
Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(options, nameof(options));
Guard.NotNull(path, nameof(path)); Guard.NotNull(path, nameof(path));
using (Stream stream = configuration.FileSystem.OpenRead(path)) using Stream stream = options.Configuration.FileSystem.OpenRead(path);
{ return Load(options, stream, out format);
return Load<TPixel>(configuration, stream, decoder);
}
} }
} }
} }

407
src/ImageSharp/Image.FromStream.cs

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

101
src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs

@ -6,6 +6,7 @@ using System.Buffers.Binary;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Text; using System.Text;
using SixLabors.ImageSharp.Metadata.Profiles.IPTC;
namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
{ {
@ -20,6 +21,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
private const uint MaxStandardDataTagSize = 0x7FFF; private const uint MaxStandardDataTagSize = 0x7FFF;
/// <summary>
/// 1:90 Coded Character Set.
/// </summary>
private const byte IptcEnvelopeCodedCharacterSet = 0x5A;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="IptcProfile"/> class. /// Initializes a new instance of the <see cref="IptcProfile"/> class.
/// </summary> /// </summary>
@ -64,6 +70,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
} }
} }
/// <summary>
/// Gets a byte array marking that UTF-8 encoding is used in application records.
/// </summary>
private static ReadOnlySpan<byte> CodedCharacterSetUtf8Value => new byte[] { 0x1B, 0x25, 0x47 }; // Uses C#'s optimization to refer to the data segment in the assembly directly, no allocation occurs.
/// <summary> /// <summary>
/// Gets the byte data of the IPTC profile. /// Gets the byte data of the IPTC profile.
/// </summary> /// </summary>
@ -194,6 +205,17 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
this.values.Add(new IptcValue(tag, encoding, value, strict)); this.values.Add(new IptcValue(tag, encoding, value, strict));
} }
/// <summary>
/// Sets the value of the specified tag.
/// </summary>
/// <param name="tag">The tag of the iptc value.</param>
/// <param name="value">The value.</param>
/// <param name="strict">
/// Indicates if length restrictions from the specification should be followed strictly.
/// Defaults to true.
/// </param>
public void SetValue(IptcTag tag, string value, bool strict = true) => this.SetValue(tag, Encoding.UTF8, value, strict);
/// <summary> /// <summary>
/// Makes sure the datetime is formatted according to the iptc specification. /// Makes sure the datetime is formatted according to the iptc specification.
/// <example> /// <example>
@ -219,17 +241,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
this.SetValue(tag, Encoding.UTF8, formattedDate); this.SetValue(tag, Encoding.UTF8, formattedDate);
} }
/// <summary>
/// Sets the value of the specified tag.
/// </summary>
/// <param name="tag">The tag of the iptc value.</param>
/// <param name="value">The value.</param>
/// <param name="strict">
/// Indicates if length restrictions from the specification should be followed strictly.
/// Defaults to true.
/// </param>
public void SetValue(IptcTag tag, string value, bool strict = true) => this.SetValue(tag, Encoding.UTF8, value, strict);
/// <summary> /// <summary>
/// Updates the data of the profile. /// Updates the data of the profile.
/// </summary> /// </summary>
@ -241,12 +252,25 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
length += value.Length + 5; length += value.Length + 5;
} }
bool hasValuesInUtf8 = this.HasValuesInUtf8();
if (hasValuesInUtf8)
{
// Additional length for UTF-8 Tag.
length += 5 + CodedCharacterSetUtf8Value.Length;
}
this.Data = new byte[length]; this.Data = new byte[length];
int offset = 0;
if (hasValuesInUtf8)
{
// Write Envelope Record.
offset = this.WriteRecord(offset, CodedCharacterSetUtf8Value, IptcRecordNumber.Envelope, IptcEnvelopeCodedCharacterSet);
}
int i = 0;
foreach (IptcValue value in this.Values) foreach (IptcValue value in this.Values)
{ {
// Standard DataSet Tag // Write Application Record.
// +-----------+----------------+---------------------------------------------------------------------------------+ // +-----------+----------------+---------------------------------------------------------------------------------+
// | Octet Pos | Name | Description | // | Octet Pos | Name | Description |
// +==========-+================+=================================================================================+ // +==========-+================+=================================================================================+
@ -263,17 +287,26 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
// | | Octet Count | the following data field(32767 or fewer octets). Note that the value of bit 7 of| // | | Octet Count | the following data field(32767 or fewer octets). Note that the value of bit 7 of|
// | | | octet 4(most significant bit) always will be 0. | // | | | octet 4(most significant bit) always will be 0. |
// +-----------+----------------+---------------------------------------------------------------------------------+ // +-----------+----------------+---------------------------------------------------------------------------------+
this.Data[i++] = IptcTagMarkerByte; offset = this.WriteRecord(offset, value.ToByteArray(), IptcRecordNumber.Application, (byte)value.Tag);
this.Data[i++] = 2; }
this.Data[i++] = (byte)value.Tag; }
this.Data[i++] = (byte)(value.Length >> 8);
this.Data[i++] = (byte)value.Length; private int WriteRecord(int offset, ReadOnlySpan<byte> recordData, IptcRecordNumber recordNumber, byte recordBinaryRepresentation)
if (value.Length > 0) {
{ Span<byte> data = this.Data.AsSpan(offset, 5);
Buffer.BlockCopy(value.ToByteArray(), 0, this.Data, i, value.Length); data[0] = IptcTagMarkerByte;
i += value.Length; data[1] = (byte)recordNumber;
} data[2] = recordBinaryRepresentation;
data[3] = (byte)(recordData.Length >> 8);
data[4] = (byte)recordData.Length;
offset += 5;
if (recordData.Length > 0)
{
recordData.CopyTo(this.Data.AsSpan(offset));
offset += recordData.Length;
} }
return offset;
} }
private void Initialize() private void Initialize()
@ -298,6 +331,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
bool isValidRecordNumber = recordNumber is >= 1 and <= 9; bool isValidRecordNumber = recordNumber is >= 1 and <= 9;
var tag = (IptcTag)this.Data[offset++]; var tag = (IptcTag)this.Data[offset++];
bool isValidEntry = isValidTagMarker && isValidRecordNumber; bool isValidEntry = isValidTagMarker && isValidRecordNumber;
bool isApplicationRecord = recordNumber == (byte)IptcRecordNumber.Application;
uint byteCount = BinaryPrimitives.ReadUInt16BigEndian(this.Data.AsSpan(offset, 2)); uint byteCount = BinaryPrimitives.ReadUInt16BigEndian(this.Data.AsSpan(offset, 2));
offset += 2; offset += 2;
@ -307,9 +341,9 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
break; break;
} }
if (isValidEntry && byteCount > 0 && (offset <= this.Data.Length - byteCount)) if (isValidEntry && isApplicationRecord && byteCount > 0 && (offset <= this.Data.Length - byteCount))
{ {
var iptcData = new byte[byteCount]; byte[] iptcData = new byte[byteCount];
Buffer.BlockCopy(this.Data, offset, iptcData, 0, (int)byteCount); Buffer.BlockCopy(this.Data, offset, iptcData, 0, (int)byteCount);
this.values.Add(new IptcValue(tag, iptcData, false)); this.values.Add(new IptcValue(tag, iptcData, false));
} }
@ -317,5 +351,22 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
offset += (int)byteCount; offset += (int)byteCount;
} }
} }
/// <summary>
/// Gets if any value has UTF-8 encoding.
/// </summary>
/// <returns>true if any value has UTF-8 encoding.</returns>
private bool HasValuesInUtf8()
{
foreach (IptcValue value in this.values)
{
if (value.Encoding == Encoding.UTF8)
{
return true;
}
}
return false;
}
} }
} }

21
src/ImageSharp/Metadata/Profiles/IPTC/IptcRecordNumber.cs

@ -0,0 +1,21 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Metadata.Profiles.IPTC
{
/// <summary>
/// Enum for the different record types of a IPTC value.
/// </summary>
internal enum IptcRecordNumber : byte
{
/// <summary>
/// A Envelope Record.
/// </summary>
Envelope = 0x01,
/// <summary>
/// A Application Record.
/// </summary>
Application = 0x02
}
}

52
src/ImageSharp/Processing/Extensions/ProcessingExtensions.IntegralImage.cs

@ -22,26 +22,60 @@ namespace SixLabors.ImageSharp.Processing
/// <returns>The <see cref="Buffer2D{T}"/> containing all the sums.</returns> /// <returns>The <see cref="Buffer2D{T}"/> containing all the sums.</returns>
public static Buffer2D<ulong> CalculateIntegralImage<TPixel>(this Image<TPixel> source) public static Buffer2D<ulong> CalculateIntegralImage<TPixel>(this Image<TPixel> source)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
=> CalculateIntegralImage(source.Frames.RootFrame);
/// <summary>
/// Apply an image integral. <See href="https://en.wikipedia.org/wiki/Summed-area_table"/>
/// </summary>
/// <param name="source">The image on which to apply the integral.</param>
/// <param name="bounds">The bounds within the image frame to calculate.</param>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <returns>The <see cref="Buffer2D{T}"/> containing all the sums.</returns>
public static Buffer2D<ulong> CalculateIntegralImage<TPixel>(this Image<TPixel> source, Rectangle bounds)
where TPixel : unmanaged, IPixel<TPixel>
=> CalculateIntegralImage(source.Frames.RootFrame, bounds);
/// <summary>
/// Apply an image integral. <See href="https://en.wikipedia.org/wiki/Summed-area_table"/>
/// </summary>
/// <param name="source">The image frame on which to apply the integral.</param>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <returns>The <see cref="Buffer2D{T}"/> containing all the sums.</returns>
public static Buffer2D<ulong> CalculateIntegralImage<TPixel>(this ImageFrame<TPixel> source)
where TPixel : unmanaged, IPixel<TPixel>
=> source.CalculateIntegralImage(source.Bounds());
/// <summary>
/// Apply an image integral. <See href="https://en.wikipedia.org/wiki/Summed-area_table"/>
/// </summary>
/// <param name="source">The image frame on which to apply the integral.</param>
/// <param name="bounds">The bounds within the image frame to calculate.</param>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <returns>The <see cref="Buffer2D{T}"/> containing all the sums.</returns>
public static Buffer2D<ulong> CalculateIntegralImage<TPixel>(this ImageFrame<TPixel> source, Rectangle bounds)
where TPixel : unmanaged, IPixel<TPixel>
{ {
Configuration configuration = source.GetConfiguration(); Configuration configuration = source.GetConfiguration();
int endY = source.Height; var interest = Rectangle.Intersect(bounds, source.Bounds());
int endX = source.Width; int startY = interest.Y;
int startX = interest.X;
int endY = interest.Height;
Buffer2D<ulong> intImage = configuration.MemoryAllocator.Allocate2D<ulong>(source.Width, source.Height); Buffer2D<ulong> intImage = configuration.MemoryAllocator.Allocate2D<ulong>(interest.Width, interest.Height);
ulong sumX0 = 0; ulong sumX0 = 0;
Buffer2D<TPixel> sourceBuffer = source.Frames.RootFrame.PixelBuffer; Buffer2D<TPixel> sourceBuffer = source.PixelBuffer;
using (IMemoryOwner<L8> tempRow = configuration.MemoryAllocator.Allocate<L8>(source.Width)) using (IMemoryOwner<L8> tempRow = configuration.MemoryAllocator.Allocate<L8>(interest.Width))
{ {
Span<L8> tempSpan = tempRow.GetSpan(); Span<L8> tempSpan = tempRow.GetSpan();
Span<TPixel> sourceRow = sourceBuffer.DangerousGetRowSpan(0); Span<TPixel> sourceRow = sourceBuffer.DangerousGetRowSpan(startY).Slice(startX, tempSpan.Length);
Span<ulong> destRow = intImage.DangerousGetRowSpan(0); Span<ulong> destRow = intImage.DangerousGetRowSpan(0);
PixelOperations<TPixel>.Instance.ToL8(configuration, sourceRow, tempSpan); PixelOperations<TPixel>.Instance.ToL8(configuration, sourceRow, tempSpan);
// First row // First row
for (int x = 0; x < endX; x++) for (int x = 0; x < tempSpan.Length; x++)
{ {
sumX0 += tempSpan[x].PackedValue; sumX0 += tempSpan[x].PackedValue;
destRow[x] = sumX0; destRow[x] = sumX0;
@ -52,7 +86,7 @@ namespace SixLabors.ImageSharp.Processing
// All other rows // All other rows
for (int y = 1; y < endY; y++) for (int y = 1; y < endY; y++)
{ {
sourceRow = sourceBuffer.DangerousGetRowSpan(y); sourceRow = sourceBuffer.DangerousGetRowSpan(y + startY).Slice(startX, tempSpan.Length);
destRow = intImage.DangerousGetRowSpan(y); destRow = intImage.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToL8(configuration, sourceRow, tempSpan); PixelOperations<TPixel>.Instance.ToL8(configuration, sourceRow, tempSpan);
@ -62,7 +96,7 @@ namespace SixLabors.ImageSharp.Processing
destRow[0] = sumX0 + previousDestRow[0]; destRow[0] = sumX0 + previousDestRow[0];
// Process all other colmns // Process all other colmns
for (int x = 1; x < endX; x++) for (int x = 1; x < tempSpan.Length; x++)
{ {
sumX0 += tempSpan[x].PackedValue; sumX0 += tempSpan[x].PackedValue;
destRow[x] = sumX0 + previousDestRow[x]; destRow[x] = sumX0 + previousDestRow[x];

108
src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs

@ -3,7 +3,6 @@
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -27,70 +26,33 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param> /// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
public AdaptiveThresholdProcessor(Configuration configuration, AdaptiveThresholdProcessor definition, Image<TPixel> source, Rectangle sourceRectangle) public AdaptiveThresholdProcessor(Configuration configuration, AdaptiveThresholdProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(configuration, source, sourceRectangle) : base(configuration, source, sourceRectangle)
{ => this.definition = definition;
this.definition = definition;
}
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source) protected override void OnFrameApply(ImageFrame<TPixel> source)
{ {
var intersect = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
Configuration configuration = this.Configuration; Configuration configuration = this.Configuration;
TPixel upper = this.definition.Upper.ToPixel<TPixel>(); TPixel upper = this.definition.Upper.ToPixel<TPixel>();
TPixel lower = this.definition.Lower.ToPixel<TPixel>(); TPixel lower = this.definition.Lower.ToPixel<TPixel>();
float thresholdLimit = this.definition.ThresholdLimit; float thresholdLimit = this.definition.ThresholdLimit;
int startY = intersect.Y; // ClusterSize defines the size of cluster to used to check for average.
int endY = intersect.Bottom; // Tweaked to support up to 4k wide pixels and not more. 4096 / 16 is 256 thus the '-1'
int startX = intersect.X; byte clusterSize = (byte)Math.Clamp(interest.Width / 16F, 0, 255);
int endX = intersect.Right;
int width = intersect.Width;
int height = intersect.Height;
// ClusterSize defines the size of cluster to used to check for average. Tweaked to support up to 4k wide pixels and not more. 4096 / 16 is 256 thus the '-1'
byte clusterSize = (byte)Math.Truncate((width / 16f) - 1);
Buffer2D<TPixel> sourceBuffer = source.PixelBuffer; using Buffer2D<ulong> intImage = source.CalculateIntegralImage(interest);
RowOperation operation = new(configuration, interest, source.PixelBuffer, intImage, upper, lower, thresholdLimit, clusterSize);
// Using pooled 2d buffer for integer image table and temp memory to hold Rgb24 converted pixel data. ParallelRowIterator.IterateRows<RowOperation, L8>(
using (Buffer2D<ulong> intImage = this.Configuration.MemoryAllocator.Allocate2D<ulong>(width, height)) configuration,
{ interest,
Rgba32 rgb = default; in operation);
for (int x = startX; x < endX; x++)
{
ulong sum = 0;
for (int y = startY; y < endY; y++)
{
Span<TPixel> row = sourceBuffer.DangerousGetRowSpan(y);
ref TPixel rowRef = ref MemoryMarshal.GetReference(row);
ref TPixel color = ref Unsafe.Add(ref rowRef, x);
color.ToRgba32(ref rgb);
sum += (ulong)(rgb.R + rgb.G + rgb.B);
if (x - startX != 0)
{
intImage[x - startX, y - startY] = intImage[x - startX - 1, y - startY] + sum;
}
else
{
intImage[x - startX, y - startY] = sum;
}
}
}
var operation = new RowOperation(intersect, source.PixelBuffer, intImage, upper, lower, thresholdLimit, clusterSize, startX, endX, startY);
ParallelRowIterator.IterateRows(
configuration,
intersect,
in operation);
}
} }
private readonly struct RowOperation : IRowOperation private readonly struct RowOperation : IRowOperation<L8>
{ {
private readonly Configuration configuration;
private readonly Rectangle bounds; private readonly Rectangle bounds;
private readonly Buffer2D<TPixel> source; private readonly Buffer2D<TPixel> source;
private readonly Buffer2D<ulong> intImage; private readonly Buffer2D<ulong> intImage;
@ -98,62 +60,58 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
private readonly TPixel lower; private readonly TPixel lower;
private readonly float thresholdLimit; private readonly float thresholdLimit;
private readonly int startX; private readonly int startX;
private readonly int endX;
private readonly int startY; private readonly int startY;
private readonly byte clusterSize; private readonly byte clusterSize;
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public RowOperation( public RowOperation(
Configuration configuration,
Rectangle bounds, Rectangle bounds,
Buffer2D<TPixel> source, Buffer2D<TPixel> source,
Buffer2D<ulong> intImage, Buffer2D<ulong> intImage,
TPixel upper, TPixel upper,
TPixel lower, TPixel lower,
float thresholdLimit, float thresholdLimit,
byte clusterSize, byte clusterSize)
int startX,
int endX,
int startY)
{ {
this.configuration = configuration;
this.bounds = bounds; this.bounds = bounds;
this.startX = bounds.X;
this.startY = bounds.Y;
this.source = source; this.source = source;
this.intImage = intImage; this.intImage = intImage;
this.upper = upper; this.upper = upper;
this.lower = lower; this.lower = lower;
this.thresholdLimit = thresholdLimit; this.thresholdLimit = thresholdLimit;
this.startX = startX;
this.endX = endX;
this.startY = startY;
this.clusterSize = clusterSize; this.clusterSize = clusterSize;
} }
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y) public void Invoke(int y, Span<L8> span)
{ {
Rgba32 rgb = default; Span<TPixel> rowSpan = this.source.DangerousGetRowSpan(y).Slice(this.startX, span.Length);
Span<TPixel> pixelRow = this.source.DangerousGetRowSpan(y); PixelOperations<TPixel>.Instance.ToL8(this.configuration, rowSpan, span);
for (int x = this.startX; x < this.endX; x++) int maxX = this.bounds.Width - 1;
int maxY = this.bounds.Height - 1;
for (int x = 0; x < rowSpan.Length; x++)
{ {
TPixel pixel = pixelRow[x]; int x1 = Math.Clamp(x - this.clusterSize + 1, 0, maxX);
pixel.ToRgba32(ref rgb); int x2 = Math.Min(x + this.clusterSize + 1, maxX);
int y1 = Math.Clamp(y - this.startY - this.clusterSize + 1, 0, maxY);
var x1 = Math.Max(x - this.startX - this.clusterSize + 1, 0); int y2 = Math.Min(y - this.startY + this.clusterSize + 1, maxY);
var x2 = Math.Min(x - this.startX + this.clusterSize + 1, this.bounds.Width - 1);
var y1 = Math.Max(y - this.startY - this.clusterSize + 1, 0);
var y2 = Math.Min(y - this.startY + this.clusterSize + 1, this.bounds.Height - 1);
var count = (uint)((x2 - x1) * (y2 - y1)); uint count = (uint)((x2 - x1) * (y2 - y1));
var sum = (long)Math.Min(this.intImage[x2, y2] - this.intImage[x1, y2] - this.intImage[x2, y1] + this.intImage[x1, y1], long.MaxValue); ulong sum = Math.Min(this.intImage[x2, y2] - this.intImage[x1, y2] - this.intImage[x2, y1] + this.intImage[x1, y1], ulong.MaxValue);
if ((rgb.R + rgb.G + rgb.B) * count <= sum * this.thresholdLimit) if (span[x].PackedValue * count <= sum * this.thresholdLimit)
{ {
this.source[x, y] = this.lower; rowSpan[x] = this.lower;
} }
else else
{ {
this.source[x, y] = this.upper; rowSpan[x] = this.upper;
} }
} }
} }

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

@ -3,6 +3,7 @@
using System.IO; using System.IO;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Tests; using SixLabors.ImageSharp.Tests;
@ -24,7 +25,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
private void GenericBechmark() private void GenericBechmark()
{ {
this.preloadedImageStream.Position = 0; 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))] [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) 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 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 memoryStream = new MemoryStream(this.jpegBytes);
using var bufferedStream = new BufferedReadStream(Configuration.Default, memoryStream); 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(); var spectralConverter = new NoopSpectralConverter();
decoder.ParseStream(bufferedStream, spectralConverter, cancellationToken: default); decoder.ParseStream(bufferedStream, spectralConverter, cancellationToken: default);
} }

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

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

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

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

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

@ -3,6 +3,7 @@
using System.IO; using System.IO;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Tests; using SixLabors.ImageSharp.Tests;
@ -31,8 +32,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
public IImageInfo Identify() public IImageInfo Identify()
{ {
using var memoryStream = new MemoryStream(this.jpegBytes); using var memoryStream = new MemoryStream(this.jpegBytes);
var decoder = new JpegDecoder(); IImageDecoder decoder = new JpegDecoder();
return decoder.Identify(Configuration.Default, memoryStream, default); 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 BenchmarkDotNet.Attributes;
using ImageMagick; using ImageMagick;
using Pfim; using Pfim;
using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests; using SixLabors.ImageSharp.Tests;
@ -18,7 +17,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
{ {
private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); 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; private byte[] data;
@ -40,7 +39,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
[Benchmark(Description = "ImageSharp Tga")] [Benchmark(Description = "ImageSharp Tga")]
public int TgaImageSharp() public int TgaImageSharp()
{ {
using var image = Image.Load<Bgr24>(this.data, new TgaDecoder()); using var image = Image.Load<Bgr24>(this.data);
return image.Width; return image.Width;
} }

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

@ -55,7 +55,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
public int WebpLossy() public int WebpLossy()
{ {
using var memoryStream = new MemoryStream(this.webpLossyBytes); 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; return image.Height;
} }
@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
public int WebpLossless() public int WebpLossless()
{ {
using var memoryStream = new MemoryStream(this.webpLosslessBytes); 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; 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. // Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -12,7 +11,8 @@ namespace SixLabors.ImageSharp.Benchmarks.General
{ {
private const int Height = 1024; 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; private Buffer2D<Rgba32> buffer;

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

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

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

@ -4,7 +4,7 @@
using System; using System;
using System.IO; using System.IO;
using Microsoft.DotNet.RemoteExecutor; using Microsoft.DotNet.RemoteExecutor;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata;
@ -75,11 +75,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
} }
string providerDump = BasicSerializer.Serialize(provider); string providerDump = BasicSerializer.Serialize(provider);
RemoteExecutor.Invoke( RemoteExecutor.Invoke(RunTest, providerDump, "Disco").Dispose();
RunTest,
providerDump,
"Disco")
.Dispose();
} }
[Theory] [Theory]
@ -98,11 +94,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecodeBitfields<TPixel>(TestImageProvider<TPixel> provider) public void BmpDecoder_CanDecodeBitfields<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> image = provider.GetImage(BmpDecoder)) using Image<TPixel> image = provider.GetImage(BmpDecoder);
{ image.DebugSave(provider);
image.DebugSave(provider); image.CompareToOriginal(provider);
image.CompareToOriginal(provider);
}
} }
[Theory] [Theory]
@ -111,11 +105,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_Inverted<TPixel>(TestImageProvider<TPixel> provider) public void BmpDecoder_CanDecode_Inverted<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> image = provider.GetImage(BmpDecoder)) using Image<TPixel> image = provider.GetImage(BmpDecoder);
{ image.DebugSave(provider);
image.DebugSave(provider); image.CompareToOriginal(provider);
image.CompareToOriginal(provider);
}
} }
[Theory] [Theory]
@ -124,11 +116,22 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_1Bit<TPixel>(TestImageProvider<TPixel> provider) public void BmpDecoder_CanDecode_1Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> image = provider.GetImage(BmpDecoder)) using Image<TPixel> image = provider.GetImage(BmpDecoder);
{ image.DebugSave(provider);
image.DebugSave(provider); image.CompareToOriginal(provider, new SystemDrawingReferenceDecoder());
image.CompareToOriginal(provider, new SystemDrawingReferenceDecoder()); }
}
[Theory]
[WithFile(Bit2, PixelTypes.Rgba32)]
[WithFile(Bit2Color, PixelTypes.Rgba32)]
public void BmpDecoder_CanDecode_2Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
// Reference decoder cant decode 2-bit, compare to reference output instead.
image.CompareToReferenceOutput(provider, extension: "png");
} }
[Theory] [Theory]
@ -136,11 +139,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_4Bit<TPixel>(TestImageProvider<TPixel> provider) public void BmpDecoder_CanDecode_4Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> image = provider.GetImage(BmpDecoder)) using Image<TPixel> image = provider.GetImage(BmpDecoder);
{ image.DebugSave(provider);
image.DebugSave(provider); image.CompareToOriginal(provider);
image.CompareToOriginal(provider);
}
} }
[Theory] [Theory]
@ -148,11 +149,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_8Bit<TPixel>(TestImageProvider<TPixel> provider) public void BmpDecoder_CanDecode_8Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> image = provider.GetImage(BmpDecoder)) using Image<TPixel> image = provider.GetImage(BmpDecoder);
{ image.DebugSave(provider);
image.DebugSave(provider); image.CompareToOriginal(provider);
image.CompareToOriginal(provider);
}
} }
[Theory] [Theory]
@ -160,11 +159,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_16Bit<TPixel>(TestImageProvider<TPixel> provider) public void BmpDecoder_CanDecode_16Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> image = provider.GetImage(BmpDecoder)) using Image<TPixel> image = provider.GetImage(BmpDecoder);
{ image.DebugSave(provider);
image.DebugSave(provider); image.CompareToOriginal(provider);
image.CompareToOriginal(provider);
}
} }
[Theory] [Theory]
@ -172,11 +169,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_32Bit<TPixel>(TestImageProvider<TPixel> provider) public void BmpDecoder_CanDecode_32Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> image = provider.GetImage(BmpDecoder)) using Image<TPixel> image = provider.GetImage(BmpDecoder);
{ image.DebugSave(provider);
image.DebugSave(provider); image.CompareToOriginal(provider);
image.CompareToOriginal(provider);
}
} }
[Theory] [Theory]
@ -184,11 +179,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_32BitV4Header_Fast<TPixel>(TestImageProvider<TPixel> provider) public void BmpDecoder_CanDecode_32BitV4Header_Fast<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> image = provider.GetImage(BmpDecoder)) using Image<TPixel> image = provider.GetImage(BmpDecoder);
{ image.DebugSave(provider);
image.DebugSave(provider); image.CompareToOriginal(provider);
image.CompareToOriginal(provider);
}
} }
[Theory] [Theory]
@ -199,11 +192,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
RleSkippedPixelHandling skippedPixelHandling = TestEnvironment.IsWindows ? RleSkippedPixelHandling.Black : RleSkippedPixelHandling.FirstColorOfPalette; RleSkippedPixelHandling skippedPixelHandling = TestEnvironment.IsWindows ? RleSkippedPixelHandling.Black : RleSkippedPixelHandling.FirstColorOfPalette;
using (Image<TPixel> image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = skippedPixelHandling })) BmpDecoderOptions options = new() { RleSkippedPixelHandling = skippedPixelHandling };
{
image.DebugSave(provider); using Image<TPixel> image = provider.GetImage(BmpDecoder, options);
image.CompareToOriginal(provider); image.DebugSave(provider);
} image.CompareToOriginal(provider);
} }
[Theory] [Theory]
@ -212,11 +205,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
RleSkippedPixelHandling skippedPixelHandling = TestEnvironment.IsWindows ? RleSkippedPixelHandling.Black : RleSkippedPixelHandling.FirstColorOfPalette; RleSkippedPixelHandling skippedPixelHandling = TestEnvironment.IsWindows ? RleSkippedPixelHandling.Black : RleSkippedPixelHandling.FirstColorOfPalette;
using (Image<TPixel> image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = skippedPixelHandling })) BmpDecoderOptions options = new() { RleSkippedPixelHandling = skippedPixelHandling };
{
image.DebugSave(provider); using Image<TPixel> image = provider.GetImage(BmpDecoder, options);
image.CompareToOriginal(provider); image.DebugSave(provider);
} image.CompareToOriginal(provider);
} }
[Theory] [Theory]
@ -227,13 +220,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit_WithDelta_SystemDrawingRefDecoder<TPixel>(TestImageProvider<TPixel> provider) public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit_WithDelta_SystemDrawingRefDecoder<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> 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); image.CompareToOriginal(provider, new SystemDrawingReferenceDecoder());
if (TestEnvironment.IsWindows)
{
image.CompareToOriginal(provider, new SystemDrawingReferenceDecoder());
}
} }
} }
@ -243,11 +235,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit_WithDelta_MagickRefDecoder<TPixel>(TestImageProvider<TPixel> provider) public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit_WithDelta_MagickRefDecoder<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = RleSkippedPixelHandling.FirstColorOfPalette })) BmpDecoderOptions options = new() { RleSkippedPixelHandling = RleSkippedPixelHandling.FirstColorOfPalette };
{ using Image<TPixel> image = provider.GetImage(BmpDecoder, options);
image.DebugSave(provider); image.DebugSave(provider);
image.CompareToOriginal(provider, new MagickReferenceDecoder()); image.CompareToOriginal(provider, new MagickReferenceDecoder());
}
} }
[Theory] [Theory]
@ -263,11 +254,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
provider.LimitAllocatorBufferCapacity().InBytesSqrt(400); provider.LimitAllocatorBufferCapacity().InBytesSqrt(400);
} }
using (Image<TPixel> image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = RleSkippedPixelHandling.FirstColorOfPalette })) BmpDecoderOptions options = new() { RleSkippedPixelHandling = RleSkippedPixelHandling.FirstColorOfPalette };
{ using Image<TPixel> image = provider.GetImage(BmpDecoder, options);
image.DebugSave(provider); image.DebugSave(provider);
image.CompareToOriginal(provider, new MagickReferenceDecoder()); image.CompareToOriginal(provider, new MagickReferenceDecoder());
}
} }
[Theory] [Theory]
@ -285,13 +275,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
provider.LimitAllocatorBufferCapacity().InBytesSqrt(400); provider.LimitAllocatorBufferCapacity().InBytesSqrt(400);
} }
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); image.DebugSave(provider);
// TODO: Neither System.Drawing nor MagickReferenceDecoder decode this file. // Neither System.Drawing nor MagickReferenceDecoder decode this file.
// image.CompareToOriginal(provider); // Compare to reference output instead.
} image.CompareToReferenceOutput(provider, extension: "png");
} }
[Theory] [Theory]
@ -299,13 +289,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecodeAlphaBitfields<TPixel>(TestImageProvider<TPixel> provider) public void BmpDecoder_CanDecodeAlphaBitfields<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> image = provider.GetImage(BmpDecoder)) using Image<TPixel> image = provider.GetImage(BmpDecoder);
{ image.DebugSave(provider);
image.DebugSave(provider);
// TODO: Neither System.Drawing nor MagickReferenceDecoder decode this file. // Neither System.Drawing nor MagickReferenceDecoder decode this file.
// image.CompareToOriginal(provider); // Compare to reference output instead.
} image.CompareToReferenceOutput(provider, extension: "png");
} }
[Theory] [Theory]
@ -313,11 +302,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecodeBitmap_WithAlphaChannel<TPixel>(TestImageProvider<TPixel> provider) public void BmpDecoder_CanDecodeBitmap_WithAlphaChannel<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> image = provider.GetImage(BmpDecoder)) using Image<TPixel> image = provider.GetImage(BmpDecoder);
{ image.DebugSave(provider);
image.DebugSave(provider); image.CompareToOriginal(provider, new MagickReferenceDecoder());
image.CompareToOriginal(provider, new MagickReferenceDecoder());
}
} }
[Theory] [Theory]
@ -325,17 +312,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecodeBitfields_WithUnusualBitmasks<TPixel>(TestImageProvider<TPixel> provider) public void BmpDecoder_CanDecodeBitfields_WithUnusualBitmasks<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> image = provider.GetImage(BmpDecoder)) using Image<TPixel> image = provider.GetImage(BmpDecoder);
{ image.DebugSave(provider);
image.DebugSave(provider);
// Choosing large tolerance of 6.1 here, because for some reason with the MagickReferenceDecoder the alpha channel
// 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,
// 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.
// 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%
// 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.
// 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());
image.CompareToOriginal(provider, ImageComparer.TolerantPercentage(6.1f), new MagickReferenceDecoder());
}
} }
[Theory] [Theory]
@ -344,13 +329,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecodeBmpv2<TPixel>(TestImageProvider<TPixel> provider) public void BmpDecoder_CanDecodeBmpv2<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> image = provider.GetImage(BmpDecoder)) using Image<TPixel> image = provider.GetImage(BmpDecoder);
{ image.DebugSave(provider);
image.DebugSave(provider);
// Do not validate. Reference files will fail validation. // Do not validate. Reference files will fail validation.
image.CompareToOriginal(provider, new MagickReferenceDecoder(false)); image.CompareToOriginal(provider, new MagickReferenceDecoder(false));
}
} }
[Theory] [Theory]
@ -358,11 +341,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecodeBmpv3<TPixel>(TestImageProvider<TPixel> provider) public void BmpDecoder_CanDecodeBmpv3<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> image = provider.GetImage(BmpDecoder)) using Image<TPixel> image = provider.GetImage(BmpDecoder);
{ image.DebugSave(provider);
image.DebugSave(provider); image.CompareToOriginal(provider);
image.CompareToOriginal(provider);
}
} }
[Theory] [Theory]
@ -370,11 +351,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecodeLessThanFullPalette<TPixel>(TestImageProvider<TPixel> provider) public void BmpDecoder_CanDecodeLessThanFullPalette<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> image = provider.GetImage(BmpDecoder)) using Image<TPixel> image = provider.GetImage(BmpDecoder);
{ image.DebugSave(provider);
image.DebugSave(provider); image.CompareToOriginal(provider, new MagickReferenceDecoder());
image.CompareToOriginal(provider, new MagickReferenceDecoder());
}
} }
[Theory] [Theory]
@ -383,13 +362,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecodeOversizedPalette<TPixel>(TestImageProvider<TPixel> provider) public void BmpDecoder_CanDecodeOversizedPalette<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> 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); image.CompareToOriginal(provider);
if (TestEnvironment.IsWindows)
{
image.CompareToOriginal(provider);
}
} }
} }
@ -397,39 +374,33 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
[WithFile(InvalidPaletteSize, PixelTypes.Rgba32)] [WithFile(InvalidPaletteSize, PixelTypes.Rgba32)]
public void BmpDecoder_ThrowsInvalidImageContentException_OnInvalidPaletteSize<TPixel>(TestImageProvider<TPixel> provider) public void BmpDecoder_ThrowsInvalidImageContentException_OnInvalidPaletteSize<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ => Assert.Throws<InvalidImageContentException>(() =>
Assert.Throws<InvalidImageContentException>(() =>
{ {
using (provider.GetImage(BmpDecoder)) using (provider.GetImage(BmpDecoder))
{ {
} }
}); });
}
[Theory] [Theory]
[WithFile(Rgb24jpeg, PixelTypes.Rgba32)] [WithFile(Rgb24jpeg, PixelTypes.Rgba32)]
[WithFile(Rgb24png, PixelTypes.Rgba32)] [WithFile(Rgb24png, PixelTypes.Rgba32)]
public void BmpDecoder_ThrowsNotSupportedException_OnUnsupportedBitmaps<TPixel>(TestImageProvider<TPixel> provider) public void BmpDecoder_ThrowsNotSupportedException_OnUnsupportedBitmaps<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ => Assert.Throws<NotSupportedException>(() =>
Assert.Throws<NotSupportedException>(() =>
{ {
using (provider.GetImage(BmpDecoder)) using (provider.GetImage(BmpDecoder))
{ {
} }
}); });
}
[Theory] [Theory]
[WithFile(Rgb32h52AdobeV3, PixelTypes.Rgba32)] [WithFile(Rgb32h52AdobeV3, PixelTypes.Rgba32)]
public void BmpDecoder_CanDecodeAdobeBmpv3<TPixel>(TestImageProvider<TPixel> provider) public void BmpDecoder_CanDecodeAdobeBmpv3<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> image = provider.GetImage(BmpDecoder)) using Image<TPixel> image = provider.GetImage(BmpDecoder);
{ image.DebugSave(provider);
image.DebugSave(provider); image.CompareToOriginal(provider, new MagickReferenceDecoder());
image.CompareToOriginal(provider, new MagickReferenceDecoder());
}
} }
[Theory] [Theory]
@ -437,11 +408,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecodeAdobeBmpv3_WithAlpha<TPixel>(TestImageProvider<TPixel> provider) public void BmpDecoder_CanDecodeAdobeBmpv3_WithAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> image = provider.GetImage(BmpDecoder)) using Image<TPixel> image = provider.GetImage(BmpDecoder);
{ image.DebugSave(provider);
image.DebugSave(provider); image.CompareToOriginal(provider, new MagickReferenceDecoder());
image.CompareToOriginal(provider, new MagickReferenceDecoder());
}
} }
[Theory] [Theory]
@ -449,11 +418,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecodeBmpv4<TPixel>(TestImageProvider<TPixel> provider) public void BmpDecoder_CanDecodeBmpv4<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> image = provider.GetImage(BmpDecoder)) using Image<TPixel> image = provider.GetImage(BmpDecoder);
{ image.DebugSave(provider);
image.DebugSave(provider); image.CompareToOriginal(provider);
image.CompareToOriginal(provider);
}
} }
[Theory] [Theory]
@ -462,11 +429,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecodeBmpv5<TPixel>(TestImageProvider<TPixel> provider) public void BmpDecoder_CanDecodeBmpv5<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> image = provider.GetImage(BmpDecoder)) using Image<TPixel> image = provider.GetImage(BmpDecoder);
{ image.DebugSave(provider);
image.DebugSave(provider); image.CompareToOriginal(provider);
image.CompareToOriginal(provider);
}
} }
[Theory] [Theory]
@ -474,11 +439,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_RespectsFileHeaderOffset<TPixel>(TestImageProvider<TPixel> provider) public void BmpDecoder_RespectsFileHeaderOffset<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> image = provider.GetImage(BmpDecoder)) using Image<TPixel> image = provider.GetImage(BmpDecoder);
{ image.DebugSave(provider);
image.DebugSave(provider); image.CompareToOriginal(provider);
image.CompareToOriginal(provider);
}
} }
[Theory] [Theory]
@ -486,11 +449,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel> provider) public void BmpDecoder_IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> image = provider.GetImage(BmpDecoder)) using Image<TPixel> image = provider.GetImage(BmpDecoder);
{ image.DebugSave(provider);
image.DebugSave(provider); image.CompareToOriginal(provider);
image.CompareToOriginal(provider);
}
} }
[Theory] [Theory]
@ -498,11 +459,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode4BytePerEntryPalette<TPixel>(TestImageProvider<TPixel> provider) public void BmpDecoder_CanDecode4BytePerEntryPalette<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> image = provider.GetImage(BmpDecoder)) using Image<TPixel> image = provider.GetImage(BmpDecoder);
{ image.DebugSave(provider);
image.DebugSave(provider); image.CompareToOriginal(provider);
image.CompareToOriginal(provider);
}
} }
[Theory] [Theory]
@ -521,12 +480,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void Identify_DetectsCorrectPixelType(string imagePath, int expectedPixelSize) public void Identify_DetectsCorrectPixelType(string imagePath, int expectedPixelSize)
{ {
var testFile = TestFile.Create(imagePath); var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false)) using var stream = new MemoryStream(testFile.Bytes, false);
{ IImageInfo imageInfo = Image.Identify(stream);
IImageInfo imageInfo = Image.Identify(stream); Assert.NotNull(imageInfo);
Assert.NotNull(imageInfo); Assert.Equal(expectedPixelSize, imageInfo.PixelType?.BitsPerPixel);
Assert.Equal(expectedPixelSize, imageInfo.PixelType?.BitsPerPixel);
}
} }
[Theory] [Theory]
@ -541,13 +498,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void Identify_DetectsCorrectWidthAndHeight(string imagePath, int expectedWidth, int expectedHeight) public void Identify_DetectsCorrectWidthAndHeight(string imagePath, int expectedWidth, int expectedHeight)
{ {
var testFile = TestFile.Create(imagePath); var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false)) using var stream = new MemoryStream(testFile.Bytes, false);
{ IImageInfo imageInfo = Image.Identify(stream);
IImageInfo imageInfo = Image.Identify(stream); Assert.NotNull(imageInfo);
Assert.NotNull(imageInfo); Assert.Equal(expectedWidth, imageInfo.Width);
Assert.Equal(expectedWidth, imageInfo.Width); Assert.Equal(expectedHeight, imageInfo.Height);
Assert.Equal(expectedHeight, imageInfo.Height);
}
} }
[Theory] [Theory]
@ -555,17 +510,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{ {
var testFile = TestFile.Create(imagePath); var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false)) using var stream = new MemoryStream(testFile.Bytes, false);
{ var decoder = new BmpDecoder();
var decoder = new BmpDecoder(); using Image<Rgba32> image = decoder.Decode<Rgba32>(DecoderOptions.Default, stream);
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, stream, default)) ImageMetadata meta = image.Metadata;
{ Assert.Equal(xResolution, meta.HorizontalResolution);
ImageMetadata meta = image.Metadata; Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(xResolution, meta.HorizontalResolution); Assert.Equal(resolutionUnit, meta.ResolutionUnits);
Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
}
} }
[Theory] [Theory]
@ -573,13 +524,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_Os2v2XShortHeader<TPixel>(TestImageProvider<TPixel> provider) public void BmpDecoder_CanDecode_Os2v2XShortHeader<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> image = provider.GetImage(BmpDecoder)) using Image<TPixel> image = provider.GetImage(BmpDecoder);
{ image.DebugSave(provider);
image.DebugSave(provider);
// TODO: Neither System.Drawing or MagickReferenceDecoder can correctly decode this file. // Neither System.Drawing or MagickReferenceDecoder can correctly decode this file.
// image.CompareToOriginal(provider); // Compare to reference output instead.
} image.CompareToReferenceOutput(provider, extension: "png");
} }
[Theory] [Theory]
@ -587,15 +537,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_Os2v2Header<TPixel>(TestImageProvider<TPixel> provider) public void BmpDecoder_CanDecode_Os2v2Header<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> image = provider.GetImage(BmpDecoder)) using Image<TPixel> image = provider.GetImage(BmpDecoder);
{ image.DebugSave(provider);
image.DebugSave(provider);
// TODO: System.Drawing can not decode this image. MagickReferenceDecoder can decode it, // 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. // Compare to reference output instead.
// The results are the same as the image sharp implementation. image.CompareToReferenceOutput(provider, extension: "png");
// image.CompareToOriginal(provider, new MagickReferenceDecoder());
}
} }
[Theory] [Theory]
@ -611,13 +558,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_Os2BitmapArray<TPixel>(TestImageProvider<TPixel> provider) public void BmpDecoder_CanDecode_Os2BitmapArray<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> image = provider.GetImage(BmpDecoder)) using Image<TPixel> image = provider.GetImage(BmpDecoder);
{ image.DebugSave(provider);
image.DebugSave(provider);
// TODO: Neither System.Drawing or MagickReferenceDecoder can correctly decode this file. // Neither System.Drawing or MagickReferenceDecoder can correctly decode this file.
// image.CompareToOriginal(provider); // Compare to reference output instead.
} image.CompareToReferenceOutput(provider, extension: "png");
} }
} }
} }

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

@ -21,6 +21,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
[Trait("Format", "Bmp")] [Trait("Format", "Bmp")]
public class BmpEncoderTests public class BmpEncoderTests
{ {
private static BmpDecoder BmpDecoder => new();
private static BmpEncoder BmpEncoder => new(); private static BmpEncoder BmpEncoder => new();
public static readonly TheoryData<BmpBitsPerPixel> BitsPerPixel = public static readonly TheoryData<BmpBitsPerPixel> BitsPerPixel =
@ -42,6 +44,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
new() new()
{ {
{ Bit1, BmpBitsPerPixel.Pixel1 }, { Bit1, BmpBitsPerPixel.Pixel1 },
{ Bit2, BmpBitsPerPixel.Pixel2 },
{ Bit4, BmpBitsPerPixel.Pixel4 }, { Bit4, BmpBitsPerPixel.Pixel4 },
{ Bit8, BmpBitsPerPixel.Pixel8 }, { Bit8, BmpBitsPerPixel.Pixel8 },
{ Rgb16, BmpBitsPerPixel.Pixel16 }, { Rgb16, BmpBitsPerPixel.Pixel16 },
@ -54,22 +57,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{ {
var testFile = TestFile.Create(imagePath); var testFile = TestFile.Create(imagePath);
using (Image<Rgba32> input = testFile.CreateRgba32Image()) using Image<Rgba32> input = testFile.CreateRgba32Image();
{ using var memStream = new MemoryStream();
using (var memStream = new MemoryStream()) input.Save(memStream, BmpEncoder);
{
input.Save(memStream, BmpEncoder); memStream.Position = 0;
using var output = Image.Load<Rgba32>(memStream);
memStream.Position = 0; ImageMetadata meta = output.Metadata;
using (var output = Image.Load<Rgba32>(memStream)) Assert.Equal(xResolution, meta.HorizontalResolution);
{ Assert.Equal(yResolution, meta.VerticalResolution);
ImageMetadata meta = output.Metadata; Assert.Equal(resolutionUnit, meta.ResolutionUnits);
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
}
}
} }
[Theory] [Theory]
@ -77,21 +74,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void Encode_PreserveBitsPerPixel(string imagePath, BmpBitsPerPixel bmpBitsPerPixel) public void Encode_PreserveBitsPerPixel(string imagePath, BmpBitsPerPixel bmpBitsPerPixel)
{ {
var testFile = TestFile.Create(imagePath); var testFile = TestFile.Create(imagePath);
using (Image<Rgba32> input = testFile.CreateRgba32Image()) using Image<Rgba32> input = testFile.CreateRgba32Image();
{ using var memStream = new MemoryStream();
using (var memStream = new MemoryStream()) input.Save(memStream, BmpEncoder);
{
input.Save(memStream, BmpEncoder); memStream.Position = 0;
using var output = Image.Load<Rgba32>(memStream);
memStream.Position = 0; BmpMetadata meta = output.Metadata.GetBmpMetadata();
using (var output = Image.Load<Rgba32>(memStream))
{ Assert.Equal(bmpBitsPerPixel, meta.BitsPerPixel);
BmpMetadata meta = output.Metadata.GetBmpMetadata();
Assert.Equal(bmpBitsPerPixel, meta.BitsPerPixel);
}
}
}
} }
[Theory] [Theory]
@ -203,6 +194,50 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true, customComparer: comparer); TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true, customComparer: comparer);
} }
[Theory]
[WithFile(Bit2, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel2)]
public void Encode_2Bit_WithV3Header_Works<TPixel>(
TestImageProvider<TPixel> provider,
BmpBitsPerPixel bitsPerPixel)
where TPixel : unmanaged, IPixel<TPixel>
{
// arrange
var encoder = new BmpEncoder() { BitsPerPixel = bitsPerPixel };
using var memoryStream = new MemoryStream();
using Image<TPixel> input = provider.GetImage(BmpDecoder);
// act
encoder.Encode(input, memoryStream);
memoryStream.Position = 0;
// assert
using var actual = Image.Load<TPixel>(memoryStream);
ImageSimilarityReport similarityReport = ImageComparer.Exact.CompareImagesOrFrames(input, actual);
Assert.True(similarityReport.IsEmpty, "encoded image does not match reference image");
}
[Theory]
[WithFile(Bit2, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel2)]
public void Encode_2Bit_WithV4Header_Works<TPixel>(
TestImageProvider<TPixel> provider,
BmpBitsPerPixel bitsPerPixel)
where TPixel : unmanaged, IPixel<TPixel>
{
// arrange
var encoder = new BmpEncoder() { BitsPerPixel = bitsPerPixel };
using var memoryStream = new MemoryStream();
using Image<TPixel> input = provider.GetImage(BmpDecoder);
// act
encoder.Encode(input, memoryStream);
memoryStream.Position = 0;
// assert
using var actual = Image.Load<TPixel>(memoryStream);
ImageSimilarityReport similarityReport = ImageComparer.Exact.CompareImagesOrFrames(input, actual);
Assert.True(similarityReport.IsEmpty, "encoded image does not match reference image");
}
[Theory] [Theory]
[WithFile(Bit1, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel1)] [WithFile(Bit1, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel1)]
public void Encode_1Bit_WithV3Header_Works<TPixel>( public void Encode_1Bit_WithV3Header_Works<TPixel>(
@ -236,28 +271,26 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
return; 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()
BitsPerPixel = BmpBitsPerPixel.Pixel8, };
Quantizer = new WuQuantizer()
}; string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "bmp", encoder, appendPixelTypeToFileName: false);
string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "bmp", encoder, appendPixelTypeToFileName: false);
// Use the default decoder to test our encoded image. This verifies the content.
// 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.
// We do not verify the reference image though as some are invalid. IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile);
IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); using FileStream stream = File.OpenRead(actualOutputFile);
using (var referenceImage = Image.Load<TPixel>(actualOutputFile, referenceDecoder)) using Image<TPixel> referenceImage = referenceDecoder.Decode<TPixel>(DecoderOptions.Default, stream, default);
{ referenceImage.CompareToReferenceOutput(
referenceImage.CompareToReferenceOutput( ImageComparer.TolerantPercentage(0.01f),
ImageComparer.TolerantPercentage(0.01f), provider,
provider, extension: "bmp",
extension: "bmp", appendPixelTypeToFileName: false,
appendPixelTypeToFileName: false, decoder: new MagickReferenceDecoder(false));
decoder: new MagickReferenceDecoder(false));
}
}
} }
[Theory] [Theory]
@ -270,28 +303,25 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
return; 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()
BitsPerPixel = BmpBitsPerPixel.Pixel8, };
Quantizer = new OctreeQuantizer() string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "bmp", encoder, appendPixelTypeToFileName: false);
};
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.
// Use the default decoder to test our encoded image. This verifies the content. IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile);
// We do not verify the reference image though as some are invalid. using FileStream stream = File.OpenRead(actualOutputFile);
IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); using Image<TPixel> referenceImage = referenceDecoder.Decode<TPixel>(DecoderOptions.Default, stream, default);
using (var referenceImage = Image.Load<TPixel>(actualOutputFile, referenceDecoder)) referenceImage.CompareToReferenceOutput(
{ ImageComparer.TolerantPercentage(0.01f),
referenceImage.CompareToReferenceOutput( provider,
ImageComparer.TolerantPercentage(0.01f), extension: "bmp",
provider, appendPixelTypeToFileName: false,
extension: "bmp", decoder: new MagickReferenceDecoder(false));
appendPixelTypeToFileName: false,
decoder: new MagickReferenceDecoder(false));
}
}
} }
[Theory] [Theory]
@ -305,26 +335,20 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void Encode_PreservesColorProfile<TPixel>(TestImageProvider<TPixel> provider) public void Encode_PreservesColorProfile<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> input = provider.GetImage(new BmpDecoder())) using Image<TPixel> input = provider.GetImage(new BmpDecoder(), new());
{ ImageSharp.Metadata.Profiles.Icc.IccProfile expectedProfile = input.Metadata.IccProfile;
ImageSharp.Metadata.Profiles.Icc.IccProfile expectedProfile = input.Metadata.IccProfile; byte[] expectedProfileBytes = expectedProfile.ToByteArray();
byte[] expectedProfileBytes = expectedProfile.ToByteArray();
using var memStream = new MemoryStream();
using (var memStream = new MemoryStream()) input.Save(memStream, new BmpEncoder());
{
input.Save(memStream, new BmpEncoder()); memStream.Position = 0;
using var output = Image.Load<Rgba32>(memStream);
memStream.Position = 0; ImageSharp.Metadata.Profiles.Icc.IccProfile actualProfile = output.Metadata.IccProfile;
using (var output = Image.Load<Rgba32>(memStream)) byte[] actualProfileBytes = actualProfile.ToByteArray();
{
ImageSharp.Metadata.Profiles.Icc.IccProfile actualProfile = output.Metadata.IccProfile; Assert.NotNull(actualProfile);
byte[] actualProfileBytes = actualProfile.ToByteArray(); Assert.Equal(expectedProfileBytes, actualProfileBytes);
Assert.NotNull(actualProfile);
Assert.Equal(expectedProfileBytes, actualProfileBytes);
}
}
}
} }
[Theory] [Theory]
@ -357,27 +381,27 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
BmpBitsPerPixel bitsPerPixel, BmpBitsPerPixel bitsPerPixel,
bool supportTransparency = true, // if set to true, will write a V4 header, otherwise a V3 header. bool supportTransparency = true, // if set to true, will write a V4 header, otherwise a V3 header.
IQuantizer quantizer = null, IQuantizer quantizer = null,
ImageComparer customComparer = null) ImageComparer customComparer = null,
IImageDecoder referenceDecoder = null)
where TPixel : unmanaged, IPixel<TPixel> 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. image.Mutate(c => c.MakeOpaque());
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);
} }
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) public void ResolutionShouldChange<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> image = provider.GetImage()) using Image<TPixel> image = provider.GetImage();
{ image.Metadata.VerticalResolution = 150;
image.Metadata.VerticalResolution = 150; image.Metadata.HorizontalResolution = 150;
image.Metadata.HorizontalResolution = 150; image.DebugSave(provider);
image.DebugSave(provider);
}
} }
[Fact] [Fact]
@ -60,11 +58,9 @@ namespace SixLabors.ImageSharp.Tests.Formats
foreach (TestFile file in Files) foreach (TestFile file in Files)
{ {
using (Image<Rgba32> image = file.CreateRgba32Image()) using Image<Rgba32> image = file.CreateRgba32Image();
{ string filename = Path.Combine(path, $"{file.FileNameWithoutExtension}.txt");
string filename = Path.Combine(path, $"{file.FileNameWithoutExtension}.txt"); File.WriteAllText(filename, image.ToBase64String(PngFormat.Instance));
File.WriteAllText(filename, image.ToBase64String(PngFormat.Instance));
}
} }
} }
@ -75,10 +71,8 @@ namespace SixLabors.ImageSharp.Tests.Formats
foreach (TestFile file in Files) foreach (TestFile file in Files)
{ {
using (Image<Rgba32> image = file.CreateRgba32Image()) using Image<Rgba32> image = file.CreateRgba32Image();
{ image.Save(Path.Combine(path, file.FileName));
image.Save(Path.Combine(path, file.FileName));
}
} }
} }
@ -120,42 +114,40 @@ namespace SixLabors.ImageSharp.Tests.Formats
foreach (TestFile file in Files) 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.SaveAsTga(output);
{ }
image.SaveAsBmp(output);
} using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.tiff")))
{
using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.jpg"))) image.SaveAsTiff(output);
{
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);
}
} }
} }
} }
@ -176,10 +168,8 @@ namespace SixLabors.ImageSharp.Tests.Formats
serialized = memoryStream.ToArray(); serialized = memoryStream.ToArray();
} }
using (var image2 = Image.Load<Rgba32>(serialized)) using var image2 = Image.Load<Rgba32>(serialized);
{ image2.Save($"{path}{Path.DirectorySeparatorChar}{file.FileName}");
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) public void CanIdentifyImageLoadedFromBytes(int width, int height, string extension)
{ {
using (var image = Image.LoadPixelData(new Rgba32[width * height], width, height)) using var image = Image.LoadPixelData<Rgba32>(new Rgba32[width * height], width, height);
{ using var memoryStream = new MemoryStream();
using (var memoryStream = new MemoryStream()) IImageFormat format = GetFormat(extension);
{ image.Save(memoryStream, format);
IImageFormat format = GetFormat(extension); memoryStream.Position = 0;
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.Width, width);
Assert.Equal(imageInfo.Height, height); Assert.Equal(imageInfo.Height, height);
memoryStream.Position = 0; 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] [Fact]
public void IdentifyReturnsNullWithInvalidStream() public void IdentifyReturnsNullWithInvalidStream()
{ {
var invalid = new byte[10]; byte[] invalid = new byte[10];
using (var memoryStream = new MemoryStream(invalid)) using var memoryStream = new MemoryStream(invalid);
{ IImageInfo imageInfo = Image.Identify(memoryStream, out IImageFormat format);
IImageInfo imageInfo = Image.Identify(memoryStream, out IImageFormat format);
Assert.Null(imageInfo); Assert.Null(imageInfo);
Assert.Null(format); Assert.Null(format);
}
} }
private static IImageFormat GetFormat(string format) private static IImageFormat GetFormat(string format)

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

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

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

@ -4,6 +4,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -58,51 +60,39 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
[Fact] [Fact]
public void Decode_IgnoreMetadataIsFalse_CommentsAreRead() public void Decode_IgnoreMetadataIsFalse_CommentsAreRead()
{ {
var options = new GifDecoder
{
IgnoreMetadata = false
};
var testFile = TestFile.Create(TestImages.Gif.Rings); var testFile = TestFile.Create(TestImages.Gif.Rings);
using (Image<Rgba32> image = testFile.CreateRgba32Image(options)) using Image<Rgba32> image = testFile.CreateRgba32Image(new GifDecoder());
{ GifMetadata metadata = image.Metadata.GetGifMetadata();
GifMetadata metadata = image.Metadata.GetGifMetadata(); Assert.Equal(1, metadata.Comments.Count);
Assert.Equal(1, metadata.Comments.Count); Assert.Equal("ImageSharp", metadata.Comments[0]);
Assert.Equal("ImageSharp", metadata.Comments[0]);
}
} }
[Fact] [Fact]
public void Decode_IgnoreMetadataIsTrue_CommentsAreIgnored() public void Decode_IgnoreMetadataIsTrue_CommentsAreIgnored()
{ {
var options = new GifDecoder DecoderOptions options = new()
{ {
IgnoreMetadata = true SkipMetadata = true
}; };
var testFile = TestFile.Create(TestImages.Gif.Rings); var testFile = TestFile.Create(TestImages.Gif.Rings);
using (Image<Rgba32> image = testFile.CreateRgba32Image(options)) using Image<Rgba32> image = testFile.CreateRgba32Image(new GifDecoder(), options);
{ GifMetadata metadata = image.Metadata.GetGifMetadata();
GifMetadata metadata = image.Metadata.GetGifMetadata(); Assert.Equal(0, metadata.Comments.Count);
Assert.Equal(0, metadata.Comments.Count);
}
} }
[Fact] [Fact]
public void Decode_CanDecodeLargeTextComment() public void Decode_CanDecodeLargeTextComment()
{ {
var options = new GifDecoder();
var testFile = TestFile.Create(TestImages.Gif.LargeComment); var testFile = TestFile.Create(TestImages.Gif.LargeComment);
using (Image<Rgba32> image = testFile.CreateRgba32Image(options)) using Image<Rgba32> image = testFile.CreateRgba32Image(new GifDecoder());
{ GifMetadata metadata = image.Metadata.GetGifMetadata();
GifMetadata metadata = image.Metadata.GetGifMetadata(); Assert.Equal(2, metadata.Comments.Count);
Assert.Equal(2, metadata.Comments.Count); Assert.Equal(new string('c', 349), metadata.Comments[0]);
Assert.Equal(new string('c', 349), metadata.Comments[0]); Assert.Equal("ImageSharp", metadata.Comments[1]);
Assert.Equal("ImageSharp", metadata.Comments[1]);
}
} }
[Fact] [Fact]
@ -111,20 +101,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
var decoder = new GifDecoder(); var decoder = new GifDecoder();
var testFile = TestFile.Create(TestImages.Gif.LargeComment); var testFile = TestFile.Create(TestImages.Gif.LargeComment);
using (Image<Rgba32> input = testFile.CreateRgba32Image(decoder)) using Image<Rgba32> input = testFile.CreateRgba32Image(decoder);
using (var memoryStream = new MemoryStream()) using var memoryStream = new MemoryStream();
{ input.Save(memoryStream, new GifEncoder());
input.Save(memoryStream, new GifEncoder()); memoryStream.Position = 0;
memoryStream.Position = 0;
using Image<Rgba32> image = decoder.Decode<Rgba32>(DecoderOptions.Default, memoryStream);
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, memoryStream, default)) GifMetadata metadata = image.Metadata.GetGifMetadata();
{ Assert.Equal(2, metadata.Comments.Count);
GifMetadata metadata = image.Metadata.GetGifMetadata(); Assert.Equal(new string('c', 349), metadata.Comments[0]);
Assert.Equal(2, metadata.Comments.Count); Assert.Equal("ImageSharp", metadata.Comments[1]);
Assert.Equal(new string('c', 349), metadata.Comments[0]);
Assert.Equal("ImageSharp", metadata.Comments[1]);
}
}
} }
[Theory] [Theory]
@ -132,15 +118,27 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{ {
var testFile = TestFile.Create(imagePath); var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false)) using var stream = new MemoryStream(testFile.Bytes, false);
{ var decoder = new GifDecoder();
var decoder = new GifDecoder(); IImageInfo image = decoder.Identify(DecoderOptions.Default, stream);
IImageInfo image = decoder.Identify(Configuration.Default, stream, default); ImageMetadata meta = image.Metadata;
ImageMetadata meta = image.Metadata; Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(xResolution, meta.HorizontalResolution); Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(yResolution, meta.VerticalResolution); Assert.Equal(resolutionUnit, meta.ResolutionUnits);
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] [Theory]
@ -148,17 +146,27 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{ {
var testFile = TestFile.Create(imagePath); var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false)) using var stream = new MemoryStream(testFile.Bytes, false);
{ var decoder = new GifDecoder();
var decoder = new GifDecoder(); using Image<Rgba32> image = decoder.Decode<Rgba32>(DecoderOptions.Default, stream);
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, stream, default)) ImageMetadata meta = image.Metadata;
{ Assert.Equal(xResolution, meta.HorizontalResolution);
ImageMetadata meta = image.Metadata; Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(xResolution, meta.HorizontalResolution); Assert.Equal(resolutionUnit, meta.ResolutionUnits);
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] [Theory]
@ -166,13 +174,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
public void Identify_VerifyRepeatCount(string imagePath, uint repeatCount) public void Identify_VerifyRepeatCount(string imagePath, uint repeatCount)
{ {
var testFile = TestFile.Create(imagePath); var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false)) using var stream = new MemoryStream(testFile.Bytes, false);
{ var decoder = new GifDecoder();
var decoder = new GifDecoder(); IImageInfo image = decoder.Identify(DecoderOptions.Default, stream);
IImageInfo image = decoder.Identify(Configuration.Default, stream, default); GifMetadata meta = image.Metadata.GetGifMetadata();
GifMetadata meta = image.Metadata.GetGifMetadata(); Assert.Equal(repeatCount, meta.RepeatCount);
Assert.Equal(repeatCount, meta.RepeatCount);
}
} }
[Theory] [Theory]
@ -180,15 +186,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
public void Decode_VerifyRepeatCount(string imagePath, uint repeatCount) public void Decode_VerifyRepeatCount(string imagePath, uint repeatCount)
{ {
var testFile = TestFile.Create(imagePath); var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false)) using var stream = new MemoryStream(testFile.Bytes, false);
{ var decoder = new GifDecoder();
var decoder = new GifDecoder(); using Image<Rgba32> image = decoder.Decode<Rgba32>(DecoderOptions.Default, stream);
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, stream, default)) GifMetadata meta = image.Metadata.GetGifMetadata();
{ Assert.Equal(repeatCount, meta.RepeatCount);
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;
using System.IO; using System.IO;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Metadata; 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) public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{ {
var testFile = TestFile.Create(imagePath); var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false)) using var stream = new MemoryStream(testFile.Bytes, false);
{ var decoder = new JpegDecoder();
var decoder = new JpegDecoder(); using Image image = decoder.Decode(DecoderOptions.Default, stream);
using (Image image = decoder.Decode(Configuration.Default, stream, default)) ImageMetadata meta = image.Metadata;
{ Assert.Equal(xResolution, meta.HorizontalResolution);
ImageMetadata meta = image.Metadata; Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(xResolution, meta.HorizontalResolution); Assert.Equal(resolutionUnit, meta.ResolutionUnits);
Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
}
} }
[Theory] [Theory]
@ -98,15 +95,27 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{ {
var testFile = TestFile.Create(imagePath); var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false)) using var stream = new MemoryStream(testFile.Bytes, false);
{ var decoder = new JpegDecoder();
var decoder = new JpegDecoder(); IImageInfo image = decoder.Identify(DecoderOptions.Default, stream);
IImageInfo image = decoder.Identify(Configuration.Default, stream, default); ImageMetadata meta = image.Metadata;
ImageMetadata meta = image.Metadata; Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(xResolution, meta.HorizontalResolution); Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(yResolution, meta.VerticalResolution); Assert.Equal(resolutionUnit, meta.ResolutionUnits);
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] [Theory]
@ -114,13 +123,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
public void Identify_VerifyQuality(string imagePath, int quality) public void Identify_VerifyQuality(string imagePath, int quality)
{ {
var testFile = TestFile.Create(imagePath); var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false)) using var stream = new MemoryStream(testFile.Bytes, false);
{ var decoder = new JpegDecoder();
var decoder = new JpegDecoder(); IImageInfo image = decoder.Identify(DecoderOptions.Default, stream);
IImageInfo image = decoder.Identify(Configuration.Default, stream, default); JpegMetadata meta = image.Metadata.GetJpegMetadata();
JpegMetadata meta = image.Metadata.GetJpegMetadata(); Assert.Equal(quality, meta.Quality);
Assert.Equal(quality, meta.Quality);
}
} }
[Theory] [Theory]
@ -128,14 +135,21 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
public void Decode_VerifyQuality(string imagePath, int quality) public void Decode_VerifyQuality(string imagePath, int quality)
{ {
var testFile = TestFile.Create(imagePath); var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false)) using var stream = new MemoryStream(testFile.Bytes, false);
{ using Image image = JpegDecoder.Decode(DecoderOptions.Default, stream);
using (Image image = JpegDecoder.Decode(Configuration.Default, stream, default)) JpegMetadata meta = image.Metadata.GetJpegMetadata();
{ Assert.Equal(quality, meta.Quality);
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] [Theory]
@ -149,12 +163,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
public void Identify_DetectsCorrectColorType(string imagePath, JpegEncodingColor expectedColorType) public void Identify_DetectsCorrectColorType(string imagePath, JpegEncodingColor expectedColorType)
{ {
var testFile = TestFile.Create(imagePath); var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false)) using var stream = new MemoryStream(testFile.Bytes, false);
{ IImageInfo image = JpegDecoder.Identify(DecoderOptions.Default, stream);
IImageInfo image = JpegDecoder.Identify(Configuration.Default, stream, default); JpegMetadata meta = image.Metadata.GetJpegMetadata();
JpegMetadata meta = image.Metadata.GetJpegMetadata(); Assert.Equal(expectedColorType, meta.ColorType);
Assert.Equal(expectedColorType, meta.ColorType);
}
} }
[Theory] [Theory]
@ -166,28 +178,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
public void Decode_DetectsCorrectColorType<TPixel>(TestImageProvider<TPixel> provider, JpegEncodingColor expectedColorType) public void Decode_DetectsCorrectColorType<TPixel>(TestImageProvider<TPixel> provider, JpegEncodingColor expectedColorType)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> image = provider.GetImage(JpegDecoder)) using Image<TPixel> image = provider.GetImage(JpegDecoder);
{ JpegMetadata meta = image.Metadata.GetJpegMetadata();
JpegMetadata meta = image.Metadata.GetJpegMetadata(); Assert.Equal(expectedColorType, meta.ColorType);
Assert.Equal(expectedColorType, meta.ColorType);
}
} }
private static void TestImageInfo(string imagePath, IImageDecoder decoder, bool useIdentify, Action<IImageInfo> test) private static void TestImageInfo(string imagePath, IImageDecoder decoder, bool useIdentify, Action<IImageInfo> test)
{ {
var testFile = TestFile.Create(imagePath); var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false)) using var stream = new MemoryStream(testFile.Bytes, false);
if (useIdentify)
{ {
if (useIdentify) IImageInfo imageInfo = decoder.Identify(DecoderOptions.Default, stream, default);
{ test(imageInfo);
IImageInfo imageInfo = ((IImageInfoDetector)decoder).Identify(Configuration.Default, stream, default); }
test(imageInfo); else
} {
else using Image<Rgba32> img = decoder.Decode<Rgba32>(DecoderOptions.Default, stream, default);
{ test(img);
using var img = decoder.Decode<Rgba32>(Configuration.Default, stream, default);
test(img);
}
} }
} }
@ -247,23 +255,21 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[InlineData(true)] [InlineData(true)]
public void IgnoreMetadata_ControlsWhetherMetadataIsParsed(bool ignoreMetadata) 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: // Snake.jpg has both Exif and ICC profiles defined:
var testFile = TestFile.Create(TestImages.Jpeg.Baseline.Snake); 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);
Assert.Null(image.Metadata.ExifProfile); }
Assert.Null(image.Metadata.IccProfile); else
} {
else Assert.NotNull(image.Metadata.ExifProfile);
{ Assert.NotNull(image.Metadata.IccProfile);
Assert.NotNull(image.Metadata.ExifProfile);
Assert.NotNull(image.Metadata.IccProfile);
}
} }
} }
@ -313,7 +319,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Exception ex = Record.Exception(() => Exception ex = Record.Exception(() =>
{ {
using Image<TPixel> image = provider.GetImage(JpegDecoder); using Image<TPixel> image = provider.GetImage(JpegDecoder);
var clone = image.Metadata.ExifProfile.DeepClone(); ExifProfile clone = image.Metadata.ExifProfile.DeepClone();
}); });
Assert.Null(ex); Assert.Null(ex);
} }
@ -356,11 +362,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Fact] [Fact]
public void EncodedStringTags_Read() public void EncodedStringTags_Read()
{ {
using (var image = Image.Load(TestFile.GetInputFileFullPath(TestImages.Jpeg.Baseline.Calliphora_EncodedStrings))) using var image = Image.Load(TestFile.GetInputFileFullPath(TestImages.Jpeg.Baseline.Calliphora_EncodedStrings));
{ ExifProfile exif = image.Metadata.ExifProfile;
ExifProfile exif = image.Metadata.ExifProfile; VerifyEncodedStrings(exif);
VerifyEncodedStrings(exif);
}
} }
private static void VerifyEncodedStrings(ExifProfile 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.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils;
using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
@ -73,10 +75,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Fact] [Fact]
public void ParseStream_BasicPropertiesAreCorrect() public void ParseStream_BasicPropertiesAreCorrect()
{ {
JpegDecoderOptions options = new();
byte[] bytes = TestFile.Create(TestImages.Jpeg.Progressive.Progress).Bytes; byte[] bytes = TestFile.Create(TestImages.Jpeg.Progressive.Progress).Bytes;
using var ms = new MemoryStream(bytes); using var ms = new MemoryStream(bytes);
using var bufferedStream = new BufferedReadStream(Configuration.Default, ms); 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); using Image<Rgba32> image = decoder.Decode<Rgba32>(bufferedStream, cancellationToken: default);
// I don't know why these numbers are different. All I know is that the decoder works // I don't know why these numbers are different. All I know is that the decoder works
@ -118,6 +121,118 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
appendPixelTypeToFileName: false); 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] [Theory]
[WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgba32)] [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgba32)]
[WithFile(TestImages.Jpeg.Progressive.Festzug, PixelTypes.Rgba32)] [WithFile(TestImages.Jpeg.Progressive.Festzug, PixelTypes.Rgba32)]
@ -148,17 +263,22 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
var cts = new CancellationTokenSource(); var cts = new CancellationTokenSource();
string file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.Jpeg420Small); string file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.Jpeg420Small);
using var pausedStream = new PausedStream(file); using var pausedStream = new PausedStream(file);
pausedStream.OnWaiting(s => pausedStream.OnWaiting(_ =>
{ {
cts.Cancel(); cts.Cancel();
pausedStream.Release(); pausedStream.Release();
}); });
var config = Configuration.CreateDefaultInstance(); var configuration = Configuration.CreateDefaultInstance();
config.FileSystem = new SingleStreamFileSystem(pausedStream); configuration.FileSystem = new SingleStreamFileSystem(pausedStream);
DecoderOptions options = new()
{
Configuration = configuration
};
await Assert.ThrowsAsync<TaskCanceledException>(async () => 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); string file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.Jpeg420Small);
using var pausedStream = new PausedStream(file); using var pausedStream = new PausedStream(file);
pausedStream.OnWaiting(s => pausedStream.OnWaiting(_ =>
{ {
cts.Cancel(); cts.Cancel();
pausedStream.Release(); pausedStream.Release();
}); });
var config = Configuration.CreateDefaultInstance(); var configuration = Configuration.CreateDefaultInstance();
config.FileSystem = new SingleStreamFileSystem(pausedStream); 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] [Theory]
[WithFileCollection(nameof(UnsupportedTestJpegs), PixelTypes.Rgba32)] [WithFileCollection(nameof(UnsupportedTestJpegs), PixelTypes.Rgba32)]
public void ThrowsNotSupported_WithUnsupportedJpegs<TPixel>(TestImageProvider<TPixel> provider) public void ThrowsNotSupported_WithUnsupportedJpegs<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ => Assert.Throws<NotSupportedException>(() =>
Assert.Throws<NotSupportedException>(() =>
{ {
using Image<TPixel> image = provider.GetImage(JpegDecoder); using Image<TPixel> image = provider.GetImage(JpegDecoder);
}); });
}
// https://github.com/SixLabors/ImageSharp/pull/1732 // https://github.com/SixLabors/ImageSharp/pull/1732
[Theory] [Theory]
@ -198,11 +320,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
public void Issue1732_DecodesWithRgbColorSpace<TPixel>(TestImageProvider<TPixel> provider) public void Issue1732_DecodesWithRgbColorSpace<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> image = provider.GetImage(JpegDecoder)) using Image<TPixel> image = provider.GetImage(JpegDecoder);
{ image.DebugSave(provider);
image.DebugSave(provider); image.CompareToOriginal(provider);
image.CompareToOriginal(provider);
}
} }
// https://github.com/SixLabors/ImageSharp/issues/2057 // 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) public void Issue2057_DecodeWorks<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> image = provider.GetImage(JpegDecoder)) using Image<TPixel> image = provider.GetImage(JpegDecoder);
{ image.DebugSave(provider);
image.DebugSave(provider); image.CompareToOriginal(provider);
image.CompareToOriginal(provider);
}
} }
// https://github.com/SixLabors/ImageSharp/issues/2133 // 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) public void Issue2133_DeduceColorSpace<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> image = provider.GetImage(JpegDecoder)) using Image<TPixel> image = provider.GetImage(JpegDecoder);
{ image.DebugSave(provider);
image.DebugSave(provider); image.CompareToOriginal(provider);
image.CompareToOriginal(provider);
}
} }
// https://github.com/SixLabors/ImageSharp/issues/2133 // 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) public void Issue2136_DecodeWorks<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> image = provider.GetImage(JpegDecoder)) using Image<TPixel> image = provider.GetImage(JpegDecoder);
{ image.DebugSave(provider);
image.DebugSave(provider); image.CompareToOriginal(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}");
}
} }
} }
} }

8
tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs

@ -38,8 +38,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{ {
// arrange // arrange
using var input = new Image<Rgba32>(1, 1); using var input = new Image<Rgba32>(1, 1);
input.Metadata.IptcProfile = new IptcProfile(); var expectedProfile = new IptcProfile();
input.Metadata.IptcProfile.SetValue(IptcTag.Byline, "unit_test"); expectedProfile.SetValue(IptcTag.Country, "ESPAÑA");
expectedProfile.SetValue(IptcTag.City, "unit-test-city");
input.Metadata.IptcProfile = expectedProfile;
// act // act
using var memStream = new MemoryStream(); using var memStream = new MemoryStream();
@ -50,7 +52,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
using var output = Image.Load<Rgba32>(memStream); using var output = Image.Load<Rgba32>(memStream);
IptcProfile actual = output.Metadata.IptcProfile; IptcProfile actual = output.Metadata.IptcProfile;
Assert.NotNull(actual); Assert.NotNull(actual);
IEnumerable<IptcValue> values = input.Metadata.IptcProfile.Values; IEnumerable<IptcValue> values = expectedProfile.Values;
Assert.Equal(values, actual.Values); Assert.Equal(values, actual.Values);
} }

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

@ -49,8 +49,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{ {
// Calculating data from ImageSharp // Calculating data from ImageSharp
byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes; 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 ms = new MemoryStream(sourceBytes);
using var bufferedStream = new BufferedReadStream(Configuration.Default, ms); using var bufferedStream = new BufferedReadStream(Configuration.Default, ms);
@ -78,8 +79,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
// Calculating data from ImageSharp // Calculating data from ImageSharp
byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes; 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 ms = new MemoryStream(sourceBytes);
using var bufferedStream = new BufferedReadStream(Configuration.Default, ms); 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 class SpectralToPixelConversionTests
{ {
public static readonly string[] BaselineTestJpegs = public static readonly string[] BaselineTestJpegs =
{ {
TestImages.Jpeg.Baseline.Calliphora, TestImages.Jpeg.Baseline.Cmyk, TestImages.Jpeg.Baseline.Jpeg400, TestImages.Jpeg.Baseline.Calliphora, TestImages.Jpeg.Baseline.Cmyk, TestImages.Jpeg.Baseline.Jpeg400,
TestImages.Jpeg.Baseline.Jpeg444, TestImages.Jpeg.Baseline.Testorig420, TestImages.Jpeg.Baseline.Jpeg444, TestImages.Jpeg.Baseline.Testorig420,
TestImages.Jpeg.Baseline.Jpeg420Small, TestImages.Jpeg.Baseline.Bad.BadEOF, TestImages.Jpeg.Baseline.Jpeg420Small, TestImages.Jpeg.Baseline.Bad.BadEOF,
TestImages.Jpeg.Baseline.MultiScanBaselineCMYK TestImages.Jpeg.Baseline.MultiScanBaselineCMYK
}; };
public SpectralToPixelConversionTests(ITestOutputHelper output) => this.Output = output; 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); using var bufferedStream = new BufferedReadStream(Configuration.Default, ms);
// Decoding // Decoding
JpegDecoderOptions options = new();
using var converter = new SpectralConverter<TPixel>(Configuration.Default); 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); var scanDecoder = new HuffmanScanDecoder(bufferedStream, converter, cancellationToken: default);
decoder.ParseStream(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; provider.Utility.TestName = JpegDecoderTests.DecodeBaselineJpegOutputName;
// Comparison // Comparison
using (var image = new Image<TPixel>(Configuration.Default, converter.GetPixelBuffer(CancellationToken.None), new ImageMetadata())) using var image = new Image<TPixel>(Configuration.Default, converter.GetPixelBuffer(CancellationToken.None), new ImageMetadata());
using (Image<TPixel> referenceImage = provider.GetReferenceOutputImage<TPixel>(appendPixelTypeToFileName: false)) using Image<TPixel> referenceImage = provider.GetReferenceOutputImage<TPixel>(appendPixelTypeToFileName: false);
{ ImageSimilarityReport report = ImageComparer.Exact.CompareImagesOrFrames(referenceImage, image);
ImageSimilarityReport report = ImageComparer.Exact.CompareImagesOrFrames(referenceImage, image);
this.Output.WriteLine($"*** {provider.SourceFileOrDescription} ***"); this.Output.WriteLine($"*** {provider.SourceFileOrDescription} ***");
this.Output.WriteLine($"Difference: {report.DifferencePercentageString}"); this.Output.WriteLine($"Difference: {report.DifferencePercentageString}");
// ReSharper disable once PossibleInvalidOperationException // ReSharper disable once PossibleInvalidOperationException
Assert.True(report.TotalNormalizedDifference.Value < 0.005f); 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 // ReSharper disable once InconsistentNaming
public static float[] Create8x8FloatData() public static float[] Create8x8FloatData()
{ {
var result = new float[64]; float[] result = new float[64];
for (int i = 0; i < 8; i++) for (int i = 0; i < 8; i++)
{ {
for (int j = 0; j < 8; j++) for (int j = 0; j < 8; j++)
@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils
// ReSharper disable once InconsistentNaming // ReSharper disable once InconsistentNaming
public static int[] Create8x8IntData() public static int[] Create8x8IntData()
{ {
var result = new int[64]; int[] result = new int[64];
for (int i = 0; i < 8; i++) for (int i = 0; i < 8; i++)
{ {
for (int j = 0; j < 8; j++) for (int j = 0; j < 8; j++)
@ -55,7 +55,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils
// ReSharper disable once InconsistentNaming // ReSharper disable once InconsistentNaming
public static short[] Create8x8ShortData() public static short[] Create8x8ShortData()
{ {
var result = new short[64]; short[] result = new short[64];
for (int i = 0; i < 8; i++) for (int i = 0; i < 8; i++)
{ {
for (int j = 0; j < 8; j++) 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) public static int[] Create8x8RandomIntData(int minValue, int maxValue, int seed = 42)
{ {
var rnd = new Random(seed); var rnd = new Random(seed);
var result = new int[64]; int[] result = new int[64];
for (int i = 0; i < 8; i++) for (int i = 0; i < 8; i++)
{ {
for (int j = 0; j < 8; j++) 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 ms = new MemoryStream(bytes);
using var bufferedStream = new BufferedReadStream(Configuration.Default, ms); 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) if (metaDataOnly)
{ {
decoder.Identify(bufferedStream, cancellationToken: default); decoder.Identify(bufferedStream, cancellationToken: default);

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

@ -1,9 +1,12 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using System;
using System.IO; using System.IO;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.Formats.Pbm;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using Xunit; using Xunit;
using static SixLabors.ImageSharp.Tests.TestImages.Pbm; using static SixLabors.ImageSharp.Tests.TestImages.Pbm;
@ -97,5 +100,27 @@ namespace SixLabors.ImageSharp.Tests.Formats.Pbm
bool isGrayscale = extension is "pgm" or "pbm"; bool isGrayscale = extension is "pgm" or "pbm";
image.CompareToReferenceOutput(provider, grayscale: isGrayscale); 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.Buffers.Binary;
using System.IO; using System.IO;
using System.Text; using System.Text;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -75,7 +76,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
var decoder = new PngDecoder(); var decoder = new PngDecoder();
ImageFormatException exception = 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); 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) private static string GetChunkTypeName(uint value)
{ {
var data = new byte[4]; byte[] data = new byte[4];
BinaryPrimitives.WriteUInt32BigEndian(data, value); BinaryPrimitives.WriteUInt32BigEndian(data, value);
return Encoding.ASCII.GetString(data); 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. // Writes a 1x1 32bit png header chunk containing a single black pixel.
memStream.Write(Raw1X1PngIhdrAndpHYs, 0, Raw1X1PngIhdrAndpHYs.Length); memStream.Write(Raw1X1PngIhdrAndpHYs, 0, Raw1X1PngIhdrAndpHYs.Length);
}
private static void WriteChunk(MemoryStream memStream, string chunkName) private static void WriteChunk(MemoryStream memStream, string chunkName)
{ {

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

@ -5,7 +5,7 @@ using System;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.DotNet.RemoteExecutor; using Microsoft.DotNet.RemoteExecutor;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -112,6 +112,28 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
image.CompareToOriginal(provider, ImageComparer.Exact); 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] [Theory]
[WithFile(TestImages.Png.AverageFilter3BytesPerPixel, PixelTypes.Rgba32)] [WithFile(TestImages.Png.AverageFilter3BytesPerPixel, PixelTypes.Rgba32)]
[WithFile(TestImages.Png.AverageFilter4BytesPerPixel, PixelTypes.Rgba32)] [WithFile(TestImages.Png.AverageFilter4BytesPerPixel, PixelTypes.Rgba32)]
@ -470,6 +492,30 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
Assert.Null(ex); Assert.Null(ex);
} }
// https://github.com/SixLabors/ImageSharp/issues/2209
[Theory]
[WithFile(TestImages.Png.Issue2209IndexedWithTransparency, PixelTypes.Rgba32)]
public void Issue2209_Decode_HasTransparencyIsTrue<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage(PngDecoder);
image.DebugSave(provider);
PngMetadata metadata = image.Metadata.GetPngMetadata();
Assert.True(metadata.HasTransparency);
}
// https://github.com/SixLabors/ImageSharp/issues/2209
[Theory]
[InlineData(TestImages.Png.Issue2209IndexedWithTransparency)]
public void Issue2209_Identify_HasTransparencyIsTrue(string imagePath)
{
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
IImageInfo imageInfo = Image.Identify(stream);
PngMetadata metadata = imageInfo.Metadata.GetPngMetadata();
Assert.True(metadata.HasTransparency);
}
// https://github.com/SixLabors/ImageSharp/issues/410 // https://github.com/SixLabors/ImageSharp/issues/410
[Theory] [Theory]
[WithFile(TestImages.Png.Bad.Issue410_MalformedApplePng, PixelTypes.Rgba32)] [WithFile(TestImages.Png.Bad.Issue410_MalformedApplePng, 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)] [WithTestPatternImages(nameof(PngColorTypes), 7, 5, PixelTypes.Rgba32)]
public void WorksWithDifferentSizes<TPixel>(TestImageProvider<TPixel> provider, PngColorType pngColorType) public void WorksWithDifferentSizes<TPixel>(TestImageProvider<TPixel> provider, PngColorType pngColorType)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ => TestPngEncoderCore(
TestPngEncoderCore(
provider, provider,
pngColorType, pngColorType,
PngFilterMethod.Adaptive, PngFilterMethod.Adaptive,
PngBitDepth.Bit8, PngBitDepth.Bit8,
PngInterlaceMode.None, PngInterlaceMode.None,
appendPngColorType: true); appendPngColorType: true);
}
[Theory] [Theory]
[WithTestPatternImages(nameof(PngColorTypes), 24, 24, PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24)] [WithTestPatternImages(nameof(PngColorTypes), 24, 24, PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24)]
@ -199,7 +197,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
return; return;
} }
foreach (var filterMethod in PngFilterMethods) foreach (object[] filterMethod in PngFilterMethods)
{ {
foreach (PngInterlaceMode interlaceMode in InterlaceMode) 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) public void WorksWithAllBitDepthsAndExcludeAllFilter<TPixel>(TestImageProvider<TPixel> provider, PngColorType pngColorType, PngBitDepth pngBitDepth)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
foreach (var filterMethod in PngFilterMethods) foreach (object[] filterMethod in PngFilterMethods)
{ {
foreach (PngInterlaceMode interlaceMode in InterlaceMode) 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) public void InfersColorTypeAndBitDepth<TPixel>(TestImageProvider<TPixel> provider, PngColorType pngColorType, PngBitDepth pngBitDepth)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Stream stream = new MemoryStream()) using Stream stream = new MemoryStream();
{ PngEncoder.Encode(provider.GetImage(), stream);
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(); PngMetadata metadata = image.Metadata.GetPngMetadata();
Assert.Equal(pngColorType, metadata.ColorType); Assert.Equal(pngColorType, metadata.ColorType);
Assert.Equal(pngBitDepth, metadata.BitDepth); Assert.Equal(pngBitDepth, metadata.BitDepth);
}
} }
[Theory] [Theory]
@ -329,14 +325,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
public void WritesFileMarker<TPixel>(TestImageProvider<TPixel> provider) public void WritesFileMarker<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> image = provider.GetImage()) using Image<TPixel> image = provider.GetImage();
using (var ms = new MemoryStream()) using var ms = new MemoryStream();
{ image.Save(ms, PngEncoder);
image.Save(ms, PngEncoder);
byte[] data = ms.ToArray().Take(8).ToArray(); byte[] data = ms.ToArray().Take(8).ToArray();
byte[] expected = byte[] expected =
{ {
0x89, // Set the high bit. 0x89, // Set the high bit.
0x50, // P 0x50, // P
0x4E, // N 0x4E, // N
@ -347,8 +342,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
0x0A // LF 0x0A // LF
}; };
Assert.Equal(expected, data); Assert.Equal(expected, data);
}
} }
[Theory] [Theory]
@ -356,22 +350,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{ {
var testFile = TestFile.Create(imagePath); var testFile = TestFile.Create(imagePath);
using (Image<Rgba32> input = testFile.CreateRgba32Image()) using Image<Rgba32> input = testFile.CreateRgba32Image();
{ using var memStream = new MemoryStream();
using (var memStream = new MemoryStream()) input.Save(memStream, PngEncoder);
{
input.Save(memStream, PngEncoder);
memStream.Position = 0; memStream.Position = 0;
using (var output = Image.Load<Rgba32>(memStream)) using var output = Image.Load<Rgba32>(memStream);
{ ImageMetadata meta = output.Metadata;
ImageMetadata meta = output.Metadata; Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(xResolution, meta.HorizontalResolution); Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(yResolution, meta.VerticalResolution); Assert.Equal(resolutionUnit, meta.ResolutionUnits);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
}
}
} }
[Theory] [Theory]
@ -379,21 +367,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
public void Encode_PreserveBits(string imagePath, PngBitDepth pngBitDepth) public void Encode_PreserveBits(string imagePath, PngBitDepth pngBitDepth)
{ {
var testFile = TestFile.Create(imagePath); var testFile = TestFile.Create(imagePath);
using (Image<Rgba32> input = testFile.CreateRgba32Image()) using Image<Rgba32> input = testFile.CreateRgba32Image();
{ using var memStream = new MemoryStream();
using (var memStream = new MemoryStream()) input.Save(memStream, PngEncoder);
{
input.Save(memStream, PngEncoder);
memStream.Position = 0; memStream.Position = 0;
using (var output = Image.Load<Rgba32>(memStream)) using var output = Image.Load<Rgba32>(memStream);
{ PngMetadata meta = output.Metadata.GetPngMetadata();
PngMetadata meta = output.Metadata.GetPngMetadata();
Assert.Equal(pngBitDepth, meta.BitDepth); Assert.Equal(pngBitDepth, meta.BitDepth);
}
}
}
} }
[Theory] [Theory]
@ -437,9 +419,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
memStream.Position = 0; memStream.Position = 0;
using var actual = Image.Load<Rgba32>(memStream); using var actual = Image.Load<Rgba32>(memStream);
Rgba32 expectedColor = Color.Blue; 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); 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) public void Encode_PreserveTrns(string imagePath, PngBitDepth pngBitDepth, PngColorType pngColorType)
{ {
var testFile = TestFile.Create(imagePath); 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(); case PngColorType.Grayscale:
Assert.True(inMeta.HasTransparency); 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()) break;
{ case PngColorType.Rgb:
input.Save(memStream, PngEncoder); if (pngBitDepth.Equals(PngBitDepth.Bit16))
memStream.Position = 0;
using (var output = Image.Load<Rgba32>(memStream))
{ {
PngMetadata outMeta = output.Metadata.GetPngMetadata(); Assert.True(outMeta.TransparentRgb48.HasValue);
Assert.True(outMeta.HasTransparency); Assert.Equal(inMeta.TransparentRgb48, outMeta.TransparentRgb48);
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;
}
} }
} 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) PngChunkFilter optimizeMethod = PngChunkFilter.None)
where TPixel : unmanaged, IPixel<TPixel> 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,
ColorType = pngColorType, CompressionLevel = compressionLevel,
FilterMethod = pngFilterMethod, BitDepth = bitDepth,
CompressionLevel = compressionLevel, Quantizer = new WuQuantizer(new QuantizerOptions { MaxColors = paletteSize }),
BitDepth = bitDepth, InterlaceMethod = interlaceMode,
Quantizer = new WuQuantizer(new QuantizerOptions { MaxColors = paletteSize }), ChunkFilter = optimizeMethod,
InterlaceMethod = interlaceMode, };
ChunkFilter = optimizeMethod,
};
string pngColorTypeInfo = appendPngColorType ? pngColorType.ToString() : string.Empty; string pngColorTypeInfo = appendPngColorType ? pngColorType.ToString() : string.Empty;
string pngFilterMethodInfo = appendPngFilterMethod ? pngFilterMethod.ToString() : string.Empty; string pngFilterMethodInfo = appendPngFilterMethod ? pngFilterMethod.ToString() : string.Empty;
string compressionLevelInfo = appendCompressionLevel ? $"_C{compressionLevel}" : string.Empty; string compressionLevelInfo = appendCompressionLevel ? $"_C{compressionLevel}" : string.Empty;
string paletteSizeInfo = appendPaletteSize ? $"_PaletteSize-{paletteSize}" : string.Empty; string paletteSizeInfo = appendPaletteSize ? $"_PaletteSize-{paletteSize}" : string.Empty;
string pngBitDepthInfo = appendPngBitDepth ? bitDepth.ToString() : string.Empty; string pngBitDepthInfo = appendPngBitDepth ? bitDepth.ToString() : string.Empty;
string pngInterlaceModeInfo = interlaceMode != PngInterlaceMode.None ? $"_{interlaceMode}" : 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 // We compare using both our decoder and the reference decoder as pixel transformation
// occurs within the encoder itself leaving the input image unaffected. // occurs within the encoder itself leaving the input image unaffected.
// This means we are benefiting from testing our decoder also. // This means we are benefiting from testing our decoder also.
using (var imageSharpImage = Image.Load<TPixel>(actualOutputFile, new PngDecoder())) using FileStream fileStream = File.OpenRead(actualOutputFile);
using (var referenceImage = Image.Load<TPixel>(actualOutputFile, referenceDecoder)) using Image<TPixel> imageSharpImage = new PngDecoder().Decode<TPixel>(DecoderOptions.Default, fileStream);
{
ImageComparer.Exact.VerifySimilarity(referenceImage, imageSharpImage); 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.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Exif;
@ -56,11 +57,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
public void Decoder_CanReadTextData<TPixel>(TestImageProvider<TPixel> provider) public void Decoder_CanReadTextData<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> image = provider.GetImage(new PngDecoder())) using Image<TPixel> image = provider.GetImage(new PngDecoder());
{ PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance);
PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); VerifyTextDataIsPresent(meta);
VerifyTextDataIsPresent(meta);
}
} }
[Theory] [Theory]
@ -69,18 +68,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
var decoder = new PngDecoder(); var decoder = new PngDecoder();
using (Image<TPixel> input = provider.GetImage(decoder)) using Image<TPixel> input = provider.GetImage(decoder);
using (var memoryStream = new MemoryStream()) using var memoryStream = new MemoryStream();
{ input.Save(memoryStream, new PngEncoder());
input.Save(memoryStream, new PngEncoder());
memoryStream.Position = 0;
memoryStream.Position = 0; using Image<Rgba32> image = decoder.Decode<Rgba32>(DecoderOptions.Default, memoryStream);
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, memoryStream, default)) PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance);
{ VerifyTextDataIsPresent(meta);
PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance);
VerifyTextDataIsPresent(meta);
}
}
} }
[Theory] [Theory]
@ -88,16 +83,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
public void Decoder_IgnoresInvalidTextData<TPixel>(TestImageProvider<TPixel> provider) public void Decoder_IgnoresInvalidTextData<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> image = provider.GetImage(new PngDecoder())) using Image<TPixel> image = provider.GetImage(new PngDecoder());
{ PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance);
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 "leading space"); Assert.DoesNotContain(meta.TextData, m => m.Value is "trailing 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 "space"); Assert.DoesNotContain(meta.TextData, m => m.Value is "empty");
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 "invalid characters"); Assert.DoesNotContain(meta.TextData, m => m.Value is "too large");
Assert.DoesNotContain(meta.TextData, m => m.Value is "too large");
}
} }
[Theory] [Theory]
@ -106,30 +99,27 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
var decoder = new PngDecoder(); var decoder = new PngDecoder();
using (Image<TPixel> input = provider.GetImage(decoder)) using Image<TPixel> input = provider.GetImage(decoder);
using (var memoryStream = new MemoryStream()) 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. TextCompressionThreshold = 50
var expectedText = new PngTextData("large-text", new string('c', 100), string.Empty, string.Empty); });
// This will be a iTXt chunk. memoryStream.Position = 0;
var expectedTextNoneLatin = new PngTextData("large-text-non-latin", new string('Ф', 100), "language-tag", "translated-keyword"); using Image<Rgba32> image = decoder.Decode<Rgba32>(DecoderOptions.Default, memoryStream);
PngMetadata inputMetadata = input.Metadata.GetFormatMetadata(PngFormat.Instance); PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance);
inputMetadata.TextData.Add(expectedText); Assert.Contains(meta.TextData, m => m.Equals(expectedText));
inputMetadata.TextData.Add(expectedTextNoneLatin); Assert.Contains(meta.TextData, m => m.Equals(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));
}
}
} }
[Theory] [Theory]
@ -137,17 +127,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
public void Decode_ReadsExifData<TPixel>(TestImageProvider<TPixel> provider) public void Decode_ReadsExifData<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
var decoder = new PngDecoder DecoderOptions options = new()
{ {
IgnoreMetadata = false SkipMetadata = false
}; };
using (Image<TPixel> image = provider.GetImage(decoder)) using Image<TPixel> image = provider.GetImage(new PngDecoder(), options);
{ Assert.NotNull(image.Metadata.ExifProfile);
Assert.NotNull(image.Metadata.ExifProfile); ExifProfile exif = image.Metadata.ExifProfile;
ExifProfile exif = image.Metadata.ExifProfile; VerifyExifDataIsPresent(exif);
VerifyExifDataIsPresent(exif);
}
} }
[Theory] [Theory]
@ -155,53 +143,49 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
public void Decode_IgnoresExifData_WhenIgnoreMetadataIsTrue<TPixel>(TestImageProvider<TPixel> provider) public void Decode_IgnoresExifData_WhenIgnoreMetadataIsTrue<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
var decoder = new PngDecoder DecoderOptions options = new()
{ {
IgnoreMetadata = true SkipMetadata = true
}; };
using (Image<TPixel> image = provider.GetImage(decoder)) PngDecoder decoder = new();
{
Assert.Null(image.Metadata.ExifProfile); using Image<TPixel> image = provider.GetImage(decoder, options);
} Assert.Null(image.Metadata.ExifProfile);
} }
[Fact] [Fact]
public void Decode_IgnoreMetadataIsFalse_TextChunkIsRead() public void Decode_IgnoreMetadataIsFalse_TextChunkIsRead()
{ {
var options = new PngDecoder DecoderOptions options = new()
{ {
IgnoreMetadata = false SkipMetadata = false
}; };
var testFile = TestFile.Create(TestImages.Png.Blur); var testFile = TestFile.Create(TestImages.Png.Blur);
using (Image<Rgba32> image = testFile.CreateRgba32Image(options)) using Image<Rgba32> image = testFile.CreateRgba32Image(new PngDecoder(), options);
{ PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance);
PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance);
Assert.Equal(1, meta.TextData.Count); Assert.Equal(1, meta.TextData.Count);
Assert.Equal("Software", meta.TextData[0].Keyword); Assert.Equal("Software", meta.TextData[0].Keyword);
Assert.Equal("paint.net 4.0.6", meta.TextData[0].Value); Assert.Equal("paint.net 4.0.6", meta.TextData[0].Value);
Assert.Equal(0.4545d, meta.Gamma, precision: 4); Assert.Equal(0.4545d, meta.Gamma, precision: 4);
}
} }
[Fact] [Fact]
public void Decode_IgnoreMetadataIsTrue_TextChunksAreIgnored() public void Decode_IgnoreMetadataIsTrue_TextChunksAreIgnored()
{ {
var options = new PngDecoder DecoderOptions options = new()
{ {
IgnoreMetadata = true SkipMetadata = true
}; };
var testFile = TestFile.Create(TestImages.Png.PngWithMetadata); var testFile = TestFile.Create(TestImages.Png.PngWithMetadata);
using (Image<Rgba32> image = testFile.CreateRgba32Image(options)) using Image<Rgba32> image = testFile.CreateRgba32Image(new PngDecoder(), options);
{ PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance);
PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); Assert.Equal(0, meta.TextData.Count);
Assert.Equal(0, meta.TextData.Count);
}
} }
[Theory] [Theory]
@ -209,17 +193,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{ {
var testFile = TestFile.Create(imagePath); var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false)) using var stream = new MemoryStream(testFile.Bytes, false);
{ var decoder = new PngDecoder();
var decoder = new PngDecoder(); using Image<Rgba32> image = decoder.Decode<Rgba32>(DecoderOptions.Default, stream);
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, stream, default)) ImageMetadata meta = image.Metadata;
{ Assert.Equal(xResolution, meta.HorizontalResolution);
ImageMetadata meta = image.Metadata; Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(xResolution, meta.HorizontalResolution); Assert.Equal(resolutionUnit, meta.ResolutionUnits);
Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
}
} }
[Theory] [Theory]
@ -227,26 +207,20 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
public void Encode_PreservesColorProfile<TPixel>(TestImageProvider<TPixel> provider) public void Encode_PreservesColorProfile<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> input = provider.GetImage(new PngDecoder())) using Image<TPixel> input = provider.GetImage(new PngDecoder());
{ ImageSharp.Metadata.Profiles.Icc.IccProfile expectedProfile = input.Metadata.IccProfile;
ImageSharp.Metadata.Profiles.Icc.IccProfile expectedProfile = input.Metadata.IccProfile; byte[] expectedProfileBytes = expectedProfile.ToByteArray();
byte[] expectedProfileBytes = expectedProfile.ToByteArray();
using var memStream = new MemoryStream();
using (var memStream = new MemoryStream()) input.Save(memStream, new PngEncoder());
{
input.Save(memStream, new PngEncoder()); memStream.Position = 0;
using var output = Image.Load<Rgba32>(memStream);
memStream.Position = 0; ImageSharp.Metadata.Profiles.Icc.IccProfile actualProfile = output.Metadata.IccProfile;
using (var output = Image.Load<Rgba32>(memStream)) byte[] actualProfileBytes = actualProfile.ToByteArray();
{
ImageSharp.Metadata.Profiles.Icc.IccProfile actualProfile = output.Metadata.IccProfile; Assert.NotNull(actualProfile);
byte[] actualProfileBytes = actualProfile.ToByteArray(); Assert.Equal(expectedProfileBytes, actualProfileBytes);
Assert.NotNull(actualProfile);
Assert.Equal(expectedProfileBytes, actualProfileBytes);
}
}
}
} }
[Theory] [Theory]
@ -254,15 +228,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{ {
var testFile = TestFile.Create(imagePath); var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false)) using var stream = new MemoryStream(testFile.Bytes, false);
{ var decoder = new PngDecoder();
var decoder = new PngDecoder(); IImageInfo image = decoder.Identify(DecoderOptions.Default, stream);
IImageInfo image = decoder.Identify(Configuration.Default, stream, default); ImageMetadata meta = image.Metadata;
ImageMetadata meta = image.Metadata; Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(xResolution, meta.HorizontalResolution); Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(yResolution, meta.VerticalResolution); Assert.Equal(resolutionUnit, meta.ResolutionUnits);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
} }
[Theory] [Theory]
@ -270,13 +242,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
public void Identify_ReadsTextData(string imagePath) public void Identify_ReadsTextData(string imagePath)
{ {
var testFile = TestFile.Create(imagePath); var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false)) using var stream = new MemoryStream(testFile.Bytes, false);
{ IImageInfo imageInfo = Image.Identify(stream);
IImageInfo imageInfo = Image.Identify(stream); Assert.NotNull(imageInfo);
Assert.NotNull(imageInfo); PngMetadata meta = imageInfo.Metadata.GetFormatMetadata(PngFormat.Instance);
PngMetadata meta = imageInfo.Metadata.GetFormatMetadata(PngFormat.Instance); VerifyTextDataIsPresent(meta);
VerifyTextDataIsPresent(meta);
}
} }
[Theory] [Theory]
@ -284,14 +254,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
public void Identify_ReadsExifData(string imagePath) public void Identify_ReadsExifData(string imagePath)
{ {
var testFile = TestFile.Create(imagePath); var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false)) using var stream = new MemoryStream(testFile.Bytes, false);
{ IImageInfo imageInfo = Image.Identify(stream);
IImageInfo imageInfo = Image.Identify(stream); Assert.NotNull(imageInfo);
Assert.NotNull(imageInfo); Assert.NotNull(imageInfo.Metadata.ExifProfile);
Assert.NotNull(imageInfo.Metadata.ExifProfile); ExifProfile exif = imageInfo.Metadata.ExifProfile;
ExifProfile exif = imageInfo.Metadata.ExifProfile; VerifyExifDataIsPresent(exif);
VerifyExifDataIsPresent(exif);
}
} }
private static void VerifyExifDataIsPresent(ExifProfile exif) private static void VerifyExifDataIsPresent(ExifProfile exif)
@ -323,28 +291,26 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
public void Identify_ReadsLegacyExifData(string imagePath) public void Identify_ReadsLegacyExifData(string imagePath)
{ {
var testFile = TestFile.Create(imagePath); var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false)) using var stream = new MemoryStream(testFile.Bytes, false);
{ IImageInfo imageInfo = Image.Identify(stream);
IImageInfo imageInfo = Image.Identify(stream); Assert.NotNull(imageInfo);
Assert.NotNull(imageInfo); Assert.NotNull(imageInfo.Metadata.ExifProfile);
Assert.NotNull(imageInfo.Metadata.ExifProfile);
PngMetadata meta = imageInfo.Metadata.GetFormatMetadata(PngFormat.Instance);
PngMetadata meta = imageInfo.Metadata.GetFormatMetadata(PngFormat.Instance); Assert.DoesNotContain(meta.TextData, t => t.Keyword.Equals("Raw profile type exif", StringComparison.OrdinalIgnoreCase));
Assert.DoesNotContain(meta.TextData, t => t.Keyword.Equals("Raw profile type exif", StringComparison.OrdinalIgnoreCase));
ExifProfile exif = imageInfo.Metadata.ExifProfile;
ExifProfile exif = imageInfo.Metadata.ExifProfile; Assert.Equal(0, exif.InvalidTags.Count);
Assert.Equal(0, exif.InvalidTags.Count); Assert.Equal(3, exif.Values.Count);
Assert.Equal(3, exif.Values.Count);
Assert.Equal(
Assert.Equal( "A colorful tiling of blue, red, yellow, and green 4x4 pixel blocks.",
"A colorful tiling of blue, red, yellow, and green 4x4 pixel blocks.", exif.GetValue(ExifTag.ImageDescription).Value);
exif.GetValue(ExifTag.ImageDescription).Value); Assert.Equal(
Assert.Equal( "Duplicated from basn3p02.png, then image metadata modified with exiv2",
"Duplicated from basn3p02.png, then image metadata modified with exiv2", exif.GetValue(ExifTag.ImageHistory).Value);
exif.GetValue(ExifTag.ImageHistory).Value);
Assert.Equal(42, (int)exif.GetValue(ExifTag.ImageNumber).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. // Licensed under the Six Labors Split License.
using System.IO; using System.IO;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
@ -19,84 +20,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
// does saving a file then reopening mean both files are identical??? // does saving a file then reopening mean both files are identical???
using (Image<TPixel> image = provider.GetImage()) using Image<TPixel> image = provider.GetImage();
using (var ms = new MemoryStream()) 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);
// img2.Save(provider.Utility.GetTestOutputFileName("bmp", "_loaded"), new BmpEncoder()); // 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: 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);
}
}
}*/
/* JJS: Commented out for now since the test does not take into lossy nature of indexing. // img2.Save(provider.Utility.GetTestOutputFileName("bmp", "_loaded"), new BmpEncoder());
[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]);
}
}
}
}
}
}
}
}*/
[Theory] [Theory]
[WithTestPatternImages(300, 300, PixelTypes.Rgba32)] [WithTestPatternImages(300, 300, PixelTypes.Rgba32)]
@ -104,20 +38,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
// does saving a file then reopening mean both files are identical??? // does saving a file then reopening mean both files are identical???
using (Image<TPixel> image = provider.GetImage()) using Image<TPixel> image = provider.GetImage();
using (var ms = new MemoryStream()) using var ms = new MemoryStream();
{
// image.Save(provider.Utility.GetTestOutputFileName("png")); // image.Save(provider.Utility.GetTestOutputFileName("png"));
image.Mutate(x => x.Resize(100, 100)); image.Mutate(x => x.Resize(100, 100));
// image.Save(provider.Utility.GetTestOutputFileName("png", "resize")); // image.Save(provider.Utility.GetTestOutputFileName("png", "resize"));
image.Save(ms, new PngEncoder()); image.Save(ms, new PngEncoder());
ms.Position = 0; ms.Position = 0;
using (var img2 = Image.Load<Rgba32>(ms, new PngDecoder())) using Image<Rgba32> img2 = new PngDecoder().Decode<Rgba32>(DecoderOptions.Default, ms);
{ ImageComparer.Tolerant().VerifySimilarity(image, img2);
ImageComparer.Tolerant().VerifySimilarity(image, img2);
}
}
} }
} }
} }

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

@ -1,8 +1,9 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using System;
using Microsoft.DotNet.RemoteExecutor; using Microsoft.DotNet.RemoteExecutor;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; 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] [Theory]
[WithFile(Bit16BottomLeft, PixelTypes.Rgba32)] [WithFile(Bit16BottomLeft, PixelTypes.Rgba32)]
[WithFile(Bit24BottomLeft, 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>(() => 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 [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) public void Compress_Decompress_Roundtrip_Works(byte[] data)
{ {
using (BufferedReadStream stream = CreateCompressedStream(data)) using BufferedReadStream stream = CreateCompressedStream(data);
{ byte[] buffer = new byte[data.Length];
var 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) 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) public void Compress_Works(byte[] inputData, byte[] expectedCompressedData)
{ {
var compressedData = new byte[expectedCompressedData.Length]; byte[] compressedData = new byte[expectedCompressedData.Length];
Stream streamData = CreateCompressedStream(inputData); Stream streamData = CreateCompressedStream(inputData);
streamData.Read(compressedData, 0, expectedCompressedData.Length); 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) public void Compress_Decompress_Roundtrip_Works(byte[] data)
{ {
using BufferedReadStream stream = CreateCompressedStream(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); 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); 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]; byte[] buffer = new byte[expectedResult.Length];
using var decompressor = new NoneTiffCompression(default, default, default); 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); 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]; byte[] buffer = new byte[expectedResult.Length];
using var decompressor = new PackBitsTiffCompression(MemoryAllocator.Create(), default, default); 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); Assert.Equal(expectedResult, buffer);
} }

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

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

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

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

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

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

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

@ -4,6 +4,7 @@
using System; using System;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Formats.Webp;
using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -15,7 +16,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
[Trait("Format", "Webp")] [Trait("Format", "Webp")]
public class WebpMetaDataTests public class WebpMetaDataTests
{ {
private static WebpDecoder WebpDecoder => new() { IgnoreMetadata = false }; private static WebpDecoder WebpDecoder => new();
[Theory] [Theory]
[WithFile(TestImages.Webp.Lossy.BikeWithExif, PixelTypes.Rgba32, false)] [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) public void IgnoreMetadata_ControlsWhetherExifIsParsed_WithLossyImage<TPixel>(TestImageProvider<TPixel> provider, bool ignoreMetadata)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
var decoder = new WebpDecoder { IgnoreMetadata = ignoreMetadata }; DecoderOptions options = new() { SkipMetadata = ignoreMetadata };
using Image<TPixel> image = provider.GetImage(WebpDecoder, options);
using Image<TPixel> image = provider.GetImage(decoder);
if (ignoreMetadata) if (ignoreMetadata)
{ {
Assert.Null(image.Metadata.ExifProfile); 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) public void IgnoreMetadata_ControlsWhetherExifIsParsed_WithLosslessImage<TPixel>(TestImageProvider<TPixel> provider, bool ignoreMetadata)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
var decoder = new WebpDecoder { IgnoreMetadata = ignoreMetadata }; DecoderOptions options = new() { SkipMetadata = ignoreMetadata };
using Image<TPixel> image = provider.GetImage(WebpDecoder, options);
using Image<TPixel> image = provider.GetImage(decoder);
if (ignoreMetadata) if (ignoreMetadata)
{ {
Assert.Null(image.Metadata.ExifProfile); 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) public void IgnoreMetadata_ControlsWhetherIccpIsParsed<TPixel>(TestImageProvider<TPixel> provider, bool ignoreMetadata)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
var decoder = new WebpDecoder { IgnoreMetadata = ignoreMetadata }; DecoderOptions options = new() { SkipMetadata = ignoreMetadata };
using Image<TPixel> image = provider.GetImage(WebpDecoder, options);
using Image<TPixel> image = provider.GetImage(decoder);
if (ignoreMetadata) if (ignoreMetadata)
{ {
Assert.Null(image.Metadata.IccProfile); 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) public async Task IgnoreMetadata_ControlsWhetherXmpIsParsed<TPixel>(TestImageProvider<TPixel> provider, bool ignoreMetadata)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
var decoder = new WebpDecoder { IgnoreMetadata = ignoreMetadata }; DecoderOptions options = new() { SkipMetadata = ignoreMetadata };
using Image<TPixel> image = await provider.GetImageAsync(WebpDecoder, options);
using Image<TPixel> image = await provider.GetImageAsync(decoder);
if (ignoreMetadata) if (ignoreMetadata)
{ {
Assert.Null(image.Metadata.XmpProfile); 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) public void Encode_PreservesColorProfile<TPixel>(TestImageProvider<TPixel> provider, WebpFileFormatType fileFormat)
where TPixel : unmanaged, IPixel<TPixel> 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; FileFormat = fileFormat
byte[] expectedProfileBytes = expectedProfile.ToByteArray(); });
using (var memStream = new MemoryStream()) memStream.Position = 0;
{ using var output = Image.Load<Rgba32>(memStream);
input.Save(memStream, new WebpEncoder() ImageSharp.Metadata.Profiles.Icc.IccProfile actualProfile = output.Metadata.IccProfile;
{ byte[] actualProfileBytes = actualProfile.ToByteArray();
FileFormat = fileFormat
}); Assert.NotNull(actualProfile);
Assert.Equal(expectedProfileBytes, actualProfileBytes);
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] [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. // Licensed under the Six Labors Split License.
using System;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using Xunit; using Xunit;
@ -15,101 +15,138 @@ namespace SixLabors.ImageSharp.Tests
public class Decode_Cancellation : ImageLoadTestBase public class Decode_Cancellation : ImageLoadTestBase
{ {
private bool isTestStreamSeekable; private bool isTestStreamSeekable;
private readonly SemaphoreSlim notifyWaitPositionReachedSemaphore = new SemaphoreSlim(0); private readonly SemaphoreSlim notifyWaitPositionReachedSemaphore = new(0);
private readonly SemaphoreSlim continueSemaphore = new SemaphoreSlim(0); private readonly SemaphoreSlim continueSemaphore = new(0);
private readonly CancellationTokenSource cts = new CancellationTokenSource(); private readonly CancellationTokenSource cts = new();
public Decode_Cancellation() public Decode_Cancellation() => this.TopLevelConfiguration.StreamProcessingBufferSize = 128;
{
this.TopLevelConfiguration.StreamProcessingBufferSize = 128;
}
[Theory] [Theory]
[InlineData(false)] [InlineData(false)]
[InlineData(true)] [InlineData(true)]
public async Task LoadAsync_Specific_Stream(bool isInputStreamSeekable) public Task LoadAsync_Specific_Stream(bool isInputStreamSeekable)
{ {
this.isTestStreamSeekable = isInputStreamSeekable; this.isTestStreamSeekable = isInputStreamSeekable;
_ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); _ = 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] [Theory]
[InlineData(false)] [InlineData(false)]
[InlineData(true)] [InlineData(true)]
public async Task LoadAsync_Agnostic_Stream(bool isInputStreamSeekable) public Task LoadAsync_Agnostic_Stream(bool isInputStreamSeekable)
{ {
this.isTestStreamSeekable = isInputStreamSeekable; this.isTestStreamSeekable = isInputStreamSeekable;
_ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); _ = 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] [Fact]
public async Task LoadAsync_Agnostic_Path() public Task LoadAsync_Agnostic_Path()
{ {
this.isTestStreamSeekable = true; this.isTestStreamSeekable = true;
_ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); _ = 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] [Fact]
public async Task LoadAsync_Specific_Path() public Task LoadAsync_Specific_Path()
{ {
this.isTestStreamSeekable = true; this.isTestStreamSeekable = true;
_ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); _ = 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] [Theory]
[InlineData(false)] [InlineData(false)]
[InlineData(true)] [InlineData(true)]
public async Task IdentifyAsync_Stream(bool isInputStreamSeekable) public Task IdentifyAsync_Stream(bool isInputStreamSeekable)
{ {
this.isTestStreamSeekable = isInputStreamSeekable; this.isTestStreamSeekable = isInputStreamSeekable;
_ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); _ = 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] [Fact]
public async Task IdentifyAsync_CustomConfiguration_Path() public Task IdentifyAsync_CustomConfiguration_Path()
{ {
this.isTestStreamSeekable = true; this.isTestStreamSeekable = true;
_ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); _ = 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] [Theory]
[InlineData(false)] [InlineData(false)]
[InlineData(true)] [InlineData(true)]
public async Task IdentifyWithFormatAsync_CustomConfiguration_Stream(bool isInputStreamSeekable) public Task IdentifyWithFormatAsync_CustomConfiguration_Stream(bool isInputStreamSeekable)
{ {
this.isTestStreamSeekable = isInputStreamSeekable; this.isTestStreamSeekable = isInputStreamSeekable;
_ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); _ = 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] [Fact]
public async Task IdentifyWithFormatAsync_CustomConfiguration_Path() public Task IdentifyWithFormatAsync_CustomConfiguration_Path()
{ {
this.isTestStreamSeekable = true; this.isTestStreamSeekable = true;
_ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); _ = 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] [Fact]
public async Task IdentifyWithFormatAsync_DefaultConfiguration_Stream() public Task IdentifyWithFormatAsync_DefaultConfiguration_Stream()
{ {
_ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); _ = 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() 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 = private static readonly IImageFormat ExpectedGlobalFormat =
Configuration.Default.ImageFormatsManager.FindFormatByFileExtension("bmp"); Configuration.Default.ImageFormatsManager.FindFormatByFileExtension("bmp");
[Theory] [Fact]
[InlineData(false)] public void FromBytes_GlobalConfiguration()
[InlineData(true)]
public void FromBytes_GlobalConfiguration(bool useSpan)
{ {
IImageFormat type = useSpan IImageFormat type = Image.DetectFormat(this.ActualImageSpan);
? Image.DetectFormat(this.ActualImageSpan)
: Image.DetectFormat(this.ActualImageBytes);
Assert.Equal(ExpectedGlobalFormat, type); Assert.Equal(ExpectedGlobalFormat, type);
} }
[Theory] [Fact]
[InlineData(false)] public void FromBytes_CustomConfiguration()
[InlineData(true)]
public void FromBytes_CustomConfiguration(bool useSpan)
{ {
IImageFormat type = useSpan DecoderOptions options = new()
? Image.DetectFormat(this.LocalConfiguration, this.ByteArray.AsSpan()) {
: Image.DetectFormat(this.LocalConfiguration, this.ByteArray); Configuration = this.LocalConfiguration
};
IImageFormat type = Image.DetectFormat(options, this.ByteArray);
Assert.Equal(this.LocalImageFormat, type); Assert.Equal(this.LocalImageFormat, type);
} }
@ -63,7 +60,12 @@ namespace SixLabors.ImageSharp.Tests
[Fact] [Fact]
public void FromFileSystemPath_CustomConfiguration() 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); Assert.Equal(this.LocalImageFormat, type);
} }
@ -80,14 +82,24 @@ namespace SixLabors.ImageSharp.Tests
[Fact] [Fact]
public void FromStream_CustomConfiguration() 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); Assert.Equal(this.LocalImageFormat, type);
} }
[Fact] [Fact]
public void WhenNoMatchingFormatFound_ReturnsNull() 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); Assert.Null(type);
} }
@ -104,14 +116,24 @@ namespace SixLabors.ImageSharp.Tests
[Fact] [Fact]
public async Task FromStreamAsync_CustomConfiguration() 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); Assert.Equal(this.LocalImageFormat, type);
} }
[Fact] [Fact]
public async Task WhenNoMatchingFormatFoundAsync_ReturnsNull() 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); 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 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; private static byte[] ActualImageBytes => TestFile.Create(TestImages.Bmp.F).Bytes;
@ -43,7 +43,12 @@ namespace SixLabors.ImageSharp.Tests
[Fact] [Fact]
public void FromBytes_CustomConfiguration() 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.LocalImageInfo, info);
Assert.Equal(this.LocalImageFormat, type); Assert.Equal(this.LocalImageFormat, type);
@ -61,7 +66,12 @@ namespace SixLabors.ImageSharp.Tests
[Fact] [Fact]
public void FromFileSystemPath_CustomConfiguration() 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.LocalImageInfo, info);
Assert.Equal(this.LocalImageFormat, type); Assert.Equal(this.LocalImageFormat, type);
@ -70,24 +80,20 @@ namespace SixLabors.ImageSharp.Tests
[Fact] [Fact]
public void FromStream_GlobalConfiguration() public void FromStream_GlobalConfiguration()
{ {
using (var stream = new MemoryStream(ActualImageBytes)) using var stream = new MemoryStream(ActualImageBytes);
{ IImageInfo info = Image.Identify(stream, out IImageFormat type);
IImageInfo info = Image.Identify(stream, out IImageFormat type);
Assert.NotNull(info); Assert.NotNull(info);
Assert.Equal(ExpectedGlobalFormat, type); Assert.Equal(ExpectedGlobalFormat, type);
}
} }
[Fact] [Fact]
public void FromStream_GlobalConfiguration_NoFormat() public void FromStream_GlobalConfiguration_NoFormat()
{ {
using (var stream = new MemoryStream(ActualImageBytes)) using var stream = new MemoryStream(ActualImageBytes);
{ IImageInfo info = Image.Identify(stream);
IImageInfo info = Image.Identify(stream);
Assert.NotNull(info); Assert.NotNull(info);
}
} }
[Fact] [Fact]
@ -116,7 +122,12 @@ namespace SixLabors.ImageSharp.Tests
[Fact] [Fact]
public void FromStream_CustomConfiguration() 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.LocalImageInfo, info);
Assert.Equal(this.LocalImageFormat, type); Assert.Equal(this.LocalImageFormat, type);
@ -125,7 +136,12 @@ namespace SixLabors.ImageSharp.Tests
[Fact] [Fact]
public void FromStream_CustomConfiguration_NoFormat() 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); Assert.Equal(this.LocalImageInfo, info);
} }
@ -133,7 +149,12 @@ namespace SixLabors.ImageSharp.Tests
[Fact] [Fact]
public void WhenNoMatchingFormatFound_ReturnsNull() 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(info);
Assert.Null(type); Assert.Null(type);
@ -168,26 +189,22 @@ namespace SixLabors.ImageSharp.Tests
[Fact] [Fact]
public async Task FromStreamAsync_GlobalConfiguration_NoFormat() public async Task FromStreamAsync_GlobalConfiguration_NoFormat()
{ {
using (var stream = new MemoryStream(ActualImageBytes)) using var stream = new MemoryStream(ActualImageBytes);
{ var asyncStream = new AsyncStreamWrapper(stream, () => false);
var asyncStream = new AsyncStreamWrapper(stream, () => false); IImageInfo info = await Image.IdentifyAsync(asyncStream);
IImageInfo info = await Image.IdentifyAsync(asyncStream);
Assert.NotNull(info); Assert.NotNull(info);
}
} }
[Fact] [Fact]
public async Task FromStreamAsync_GlobalConfiguration() public async Task FromStreamAsync_GlobalConfiguration()
{ {
using (var stream = new MemoryStream(ActualImageBytes)) using var stream = new MemoryStream(ActualImageBytes);
{ var asyncStream = new AsyncStreamWrapper(stream, () => false);
var asyncStream = new AsyncStreamWrapper(stream, () => false); (IImageInfo ImageInfo, IImageFormat Format) res = await Image.IdentifyWithFormatAsync(asyncStream);
(IImageInfo ImageInfo, IImageFormat Format) res = await Image.IdentifyWithFormatAsync(asyncStream);
Assert.Equal(ExpectedImageSize, res.ImageInfo.Size()); Assert.Equal(ExpectedImageSize, res.ImageInfo.Size());
Assert.Equal(ExpectedGlobalFormat, res.Format); Assert.Equal(ExpectedGlobalFormat, res.Format);
}
} }
[Fact] [Fact]
@ -244,14 +261,24 @@ namespace SixLabors.ImageSharp.Tests
[Fact] [Fact]
public async Task FromPathAsync_CustomConfiguration() 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); Assert.Equal(this.LocalImageInfo, info);
} }
[Fact] [Fact]
public async Task IdentifyWithFormatAsync_FromPath_CustomConfiguration() 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.NotNull(info.ImageInfo);
Assert.Equal(this.LocalImageFormat, info.Format); Assert.Equal(this.LocalImageFormat, info.Format);
} }
@ -276,8 +303,13 @@ namespace SixLabors.ImageSharp.Tests
[Fact] [Fact]
public async Task FromStreamAsync_CustomConfiguration() public async Task FromStreamAsync_CustomConfiguration()
{ {
DecoderOptions options = new()
{
Configuration = this.LocalConfiguration
};
var asyncStream = new AsyncStreamWrapper(this.DataStream, () => false); var asyncStream = new AsyncStreamWrapper(this.DataStream, () => false);
(IImageInfo ImageInfo, IImageFormat Format) info = await Image.IdentifyWithFormatAsync(this.LocalConfiguration, asyncStream); (IImageInfo ImageInfo, IImageFormat Format) info = await Image.IdentifyWithFormatAsync(options, asyncStream);
Assert.Equal(this.LocalImageInfo, info.ImageInfo); Assert.Equal(this.LocalImageInfo, info.ImageInfo);
Assert.Equal(this.LocalImageFormat, info.Format); Assert.Equal(this.LocalImageFormat, info.Format);
@ -286,8 +318,13 @@ namespace SixLabors.ImageSharp.Tests
[Fact] [Fact]
public async Task WhenNoMatchingFormatFoundAsync_ReturnsNull() public async Task WhenNoMatchingFormatFoundAsync_ReturnsNull()
{ {
DecoderOptions options = new()
{
Configuration = new()
};
var asyncStream = new AsyncStreamWrapper(this.DataStream, () => false); var asyncStream = new AsyncStreamWrapper(this.DataStream, () => false);
(IImageInfo ImageInfo, IImageFormat Format) info = await Image.IdentifyWithFormatAsync(new Configuration(), asyncStream); (IImageInfo ImageInfo, IImageFormat Format) info = await Image.IdentifyWithFormatAsync(options, asyncStream);
Assert.Null(info.ImageInfo); Assert.Null(info.ImageInfo);
} }

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

Loading…
Cancel
Save