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
codecov: false
# Temp disabled due to runtime preview issues.
# https://github.com/SixLabors/ImageSharp/issues/2117
#- os: macos-latest
# framework: net7.0
# sdk: 7.0.x
# sdk-preview: true
# runtime: -x64
# codecov: false
- os: windows-latest
framework: net7.0
sdk: 7.0.x
sdk-preview: true
runtime: -x64
codecov: false
#- os: windows-latest
# framework: net7.0
# sdk: 7.0.x
# sdk-preview: true
# runtime: -x64
# codecov: false
- os: ubuntu-latest
framework: net6.0
sdk: 6.0.x

22
src/ImageSharp/Advanced/AotCompilerTools.cs

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

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

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

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

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

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

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

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>
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>
/// The color palette for an 1 bit image will have 2 entry's with 4 bytes for each entry.
/// </summary>
@ -125,19 +130,14 @@ namespace SixLabors.ImageSharp.Formats.Bmp
int bytesPerLine = 4 * (((image.Width * bpp) + 31) / 32);
this.padding = bytesPerLine - (int)(image.Width * (bpp / 8F));
int colorPaletteSize = 0;
if (this.bitsPerPixel == BmpBitsPerPixel.Pixel8)
{
colorPaletteSize = ColorPaletteSize8Bit;
}
else if (this.bitsPerPixel == BmpBitsPerPixel.Pixel4)
{
colorPaletteSize = ColorPaletteSize4Bit;
}
else if (this.bitsPerPixel == BmpBitsPerPixel.Pixel1)
int colorPaletteSize = this.bitsPerPixel switch
{
colorPaletteSize = ColorPaletteSize1Bit;
}
BmpBitsPerPixel.Pixel8 => ColorPaletteSize8Bit,
BmpBitsPerPixel.Pixel4 => ColorPaletteSize4Bit,
BmpBitsPerPixel.Pixel2 => ColorPaletteSize2Bit,
BmpBitsPerPixel.Pixel1 => ColorPaletteSize1Bit,
_ => 0
};
byte[] iccProfileData = null;
int iccProfileSize = 0;
@ -322,27 +322,31 @@ namespace SixLabors.ImageSharp.Formats.Bmp
switch (this.bitsPerPixel)
{
case BmpBitsPerPixel.Pixel32:
this.Write32Bit(stream, pixels);
this.Write32BitPixelData(stream, pixels);
break;
case BmpBitsPerPixel.Pixel24:
this.Write24Bit(stream, pixels);
this.Write24BitPixelData(stream, pixels);
break;
case BmpBitsPerPixel.Pixel16:
this.Write16Bit(stream, pixels);
this.Write16BitPixelData(stream, pixels);
break;
case BmpBitsPerPixel.Pixel8:
this.Write8Bit(stream, image);
this.Write8BitPixelData(stream, image);
break;
case BmpBitsPerPixel.Pixel4:
this.Write4BitColor(stream, image);
this.Write4BitPixelData(stream, image);
break;
case BmpBitsPerPixel.Pixel2:
this.Write2BitPixelData(stream, image);
break;
case BmpBitsPerPixel.Pixel1:
this.Write1BitColor(stream, image);
this.Write1BitPixelData(stream, image);
break;
}
}
@ -351,12 +355,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp
=> this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, this.padding);
/// <summary>
/// Writes the 32bit color palette to the stream.
/// Writes 32-bit data with a color palette to the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</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>
{
using IMemoryOwner<byte> row = this.AllocateRow(pixels.Width, 4);
@ -375,12 +379,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
/// <summary>
/// Writes the 24bit color palette to the stream.
/// Writes 24-bit pixel data with a color palette to the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</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>
{
int width = pixels.Width;
@ -401,12 +405,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
/// <summary>
/// Writes the 16bit color palette to the stream.
/// Writes 16-bit pixel data with a color palette to the stream.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</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>
{
int width = pixels.Width;
@ -429,12 +433,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
/// <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>
/// <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 Write8Bit<TPixel>(Stream stream, ImageFrame<TPixel> image)
private void Write8BitPixelData<TPixel>(Stream stream, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
bool isL8 = typeof(TPixel) == typeof(L8);
@ -443,7 +447,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
if (isL8)
{
this.Write8BitGray(stream, image, colorPalette);
this.Write8BitPixelData(stream, image, colorPalette);
}
else
{
@ -480,13 +484,13 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
/// <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>
/// <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>
/// <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>
{
// Create a color palette with 256 different gray values.
@ -518,12 +522,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
/// <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>
/// <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 Write4BitColor<TPixel>(Stream stream, ImageFrame<TPixel> image)
private void Write4BitPixelData<TPixel>(Stream stream, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, new QuantizerOptions()
@ -562,12 +566,65 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
/// <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>
/// <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 Write1BitColor<TPixel>(Stream stream, ImageFrame<TPixel> image)
private void Write1BitPixelData<TPixel>(Stream stream, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
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);
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);

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

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

49
src/ImageSharp/Formats/DecoderOptions.cs

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

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

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

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

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

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

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

20
src/ImageSharp/Formats/IImageDecoder.cs

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

8
src/ImageSharp/Formats/IImageDecoderInternals.cs

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

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

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

10
src/ImageSharp/Formats/IImageInfoDetector.cs

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

16
src/ImageSharp/Formats/ISpecializedDecoderOptions.cs

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

182
src/ImageSharp/Formats/ImageDecoderExtensions.cs

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

49
src/ImageSharp/Formats/ImageDecoderUtilities.cs

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -4,7 +4,6 @@
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Runtime.CompilerServices;
@ -35,10 +34,15 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary>
private readonly byte[] buffer = new byte[4];
/// <summary>
/// The general decoder options.
/// </summary>
private readonly Configuration configuration;
/// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
private readonly bool ignoreMetadata;
private readonly bool skipMetadata;
/// <summary>
/// Gets or sets a value indicating whether to read the IHDR and tRNS chunks only.
@ -118,29 +122,28 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary>
/// Initializes a new instance of the <see cref="PngDecoderCore"/> class.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="options">The decoder options.</param>
public PngDecoderCore(Configuration configuration, IPngDecoderOptions options)
public PngDecoderCore(DecoderOptions options)
{
this.Configuration = configuration ?? Configuration.Default;
this.memoryAllocator = this.Configuration.MemoryAllocator;
this.ignoreMetadata = options.IgnoreMetadata;
this.Options = options;
this.configuration = options.Configuration;
this.skipMetadata = options.SkipMetadata;
this.memoryAllocator = this.configuration.MemoryAllocator;
}
internal PngDecoderCore(Configuration configuration, bool colorMetadataOnly)
internal PngDecoderCore(DecoderOptions options, bool colorMetadataOnly)
{
this.Configuration = configuration ?? Configuration.Default;
this.memoryAllocator = this.Configuration.MemoryAllocator;
this.Options = options;
this.colorMetadataOnly = colorMetadataOnly;
this.ignoreMetadata = true;
this.skipMetadata = true;
this.configuration = options.Configuration;
this.memoryAllocator = this.configuration.MemoryAllocator;
}
/// <inheritdoc/>
public Configuration Configuration { get; }
public DecoderOptions Options { get; }
/// <summary>
/// Gets the dimensions of the image.
/// </summary>
/// <inheritdoc/>
public Size Dimensions => new(this.header.Width, this.header.Height);
/// <inheritdoc/>
@ -199,7 +202,7 @@ namespace SixLabors.ImageSharp.Formats.Png
this.ReadInternationalTextChunk(metadata, chunk.Data.GetSpan());
break;
case PngChunkType.Exif:
if (!this.ignoreMetadata)
if (!this.skipMetadata)
{
byte[] exifData = new byte[chunk.Length];
chunk.Data.GetSpan().CopyTo(exifData);
@ -336,7 +339,7 @@ namespace SixLabors.ImageSharp.Formats.Png
break;
}
if (!this.ignoreMetadata)
if (!this.skipMetadata)
{
byte[] exifData = new byte[chunk.Length];
chunk.Data.GetSpan().CopyTo(exifData);
@ -380,8 +383,8 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary>
/// Reads the least significant bits from the byte pair with the others set to 0.
/// </summary>
/// <param name="buffer">The source buffer</param>
/// <param name="offset">THe offset</param>
/// <param name="buffer">The source buffer.</param>
/// <param name="offset">THe offset.</param>
/// <returns>The <see cref="int"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte ReadByteLittleEndian(ReadOnlySpan<byte> buffer, int offset)
@ -392,7 +395,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// specified number of bits.
/// </summary>
/// <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="buffer">The new array.</param>
/// <returns>The resulting <see cref="ReadOnlySpan{Byte}"/> array.</returns>
@ -469,7 +472,7 @@ namespace SixLabors.ImageSharp.Formats.Png
where TPixel : unmanaged, IPixel<TPixel>
{
image = Image.CreateUninitialized<TPixel>(
this.Configuration,
this.configuration,
this.header.Width,
this.header.Height,
metadata);
@ -485,7 +488,7 @@ namespace SixLabors.ImageSharp.Formats.Png
this.previousScanline?.Dispose();
this.scanline?.Dispose();
this.previousScanline = this.memoryAllocator.Allocate<byte>(this.bytesPerScanline, AllocationOptions.Clean);
this.scanline = this.Configuration.MemoryAllocator.Allocate<byte>(this.bytesPerScanline, AllocationOptions.Clean);
this.scanline = this.configuration.MemoryAllocator.Allocate<byte>(this.bytesPerScanline, AllocationOptions.Clean);
}
/// <summary>
@ -798,7 +801,7 @@ namespace SixLabors.ImageSharp.Formats.Png
case PngColorType.Rgb:
PngScanlineProcessor.ProcessRgbScanline(
this.Configuration,
this.configuration,
this.header,
scanlineSpan,
rowSpan,
@ -812,7 +815,7 @@ namespace SixLabors.ImageSharp.Formats.Png
case PngColorType.RgbWithAlpha:
PngScanlineProcessor.ProcessRgbaScanline(
this.Configuration,
this.configuration,
this.header,
scanlineSpan,
rowSpan,
@ -973,6 +976,10 @@ namespace SixLabors.ImageSharp.Formats.Png
pngMetadata.HasTransparency = true;
}
}
else if (this.pngColorType == PngColorType.Palette && alpha.Length > 0)
{
pngMetadata.HasTransparency = true;
}
}
/// <summary>
@ -1001,7 +1008,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="data">The <see cref="T:Span"/> containing the data.</param>
private void ReadTextChunk(ImageMetadata baseMetadata, PngMetadata metadata, ReadOnlySpan<byte> data)
{
if (this.ignoreMetadata)
if (this.skipMetadata)
{
return;
}
@ -1036,7 +1043,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="data">The <see cref="T:Span"/> containing the data.</param>
private void ReadCompressedTextChunk(ImageMetadata baseMetadata, PngMetadata metadata, ReadOnlySpan<byte> data)
{
if (this.ignoreMetadata)
if (this.skipMetadata)
{
return;
}
@ -1222,10 +1229,10 @@ namespace SixLabors.ImageSharp.Formats.Png
{
fixed (byte* compressedDataBase = compressedData)
{
using (IMemoryOwner<byte> destBuffer = this.memoryAllocator.Allocate<byte>(this.Configuration.StreamProcessingBufferSize))
using (IMemoryOwner<byte> destBuffer = this.memoryAllocator.Allocate<byte>(this.configuration.StreamProcessingBufferSize))
using (var memoryStreamOutput = new MemoryStream(compressedData.Length))
using (var memoryStreamInput = new UnmanagedMemoryStream(compressedDataBase, compressedData.Length))
using (var bufferedStream = new BufferedReadStream(this.Configuration, memoryStreamInput))
using (var bufferedStream = new BufferedReadStream(this.configuration, memoryStreamInput))
using (var inflateStream = new ZlibInflateStream(bufferedStream))
{
Span<byte> destUncompressedData = destBuffer.GetSpan();
@ -1324,7 +1331,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="data">The <see cref="T:Span"/> containing the data.</param>
private void ReadInternationalTextChunk(ImageMetadata metadata, ReadOnlySpan<byte> data)
{
if (this.ignoreMetadata)
if (this.skipMetadata)
{
return;
}
@ -1563,7 +1570,7 @@ namespace SixLabors.ImageSharp.Formats.Png
private IMemoryOwner<byte> ReadChunkData(int length)
{
// We rent the buffer here to return it afterwards in Decode()
IMemoryOwner<byte> buffer = this.Configuration.MemoryAllocator.Allocate<byte>(length, AllocationOptions.Clean);
IMemoryOwner<byte> buffer = this.configuration.MemoryAllocator.Allocate<byte>(length, AllocationOptions.Clean);
this.currentStream.Read(buffer.GetSpan(), 0, length);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

38
src/ImageSharp/Image.Decode.cs

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

451
src/ImageSharp/Image.FromBytes.cs

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

295
src/ImageSharp/Image.FromFile.cs

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

407
src/ImageSharp/Image.FromStream.cs

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

68
src/ImageSharp/Image.LoadPixelData.cs

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

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

@ -6,6 +6,7 @@ using System.Buffers.Binary;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
using 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;
/// <summary>
/// 1:90 Coded Character Set.
/// </summary>
private const byte IptcEnvelopeCodedCharacterSet = 0x5A;
/// <summary>
/// Initializes a new instance of the <see cref="IptcProfile"/> class.
/// </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>
/// Gets the byte data of the IPTC profile.
/// </summary>
@ -194,6 +205,17 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
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>
/// Makes sure the datetime is formatted according to the iptc specification.
/// <example>
@ -219,17 +241,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
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>
/// Updates the data of the profile.
/// </summary>
@ -241,12 +252,25 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
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];
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)
{
// Standard DataSet Tag
// Write Application Record.
// +-----------+----------------+---------------------------------------------------------------------------------+
// | 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 4(most significant bit) always will be 0. |
// +-----------+----------------+---------------------------------------------------------------------------------+
this.Data[i++] = IptcTagMarkerByte;
this.Data[i++] = 2;
this.Data[i++] = (byte)value.Tag;
this.Data[i++] = (byte)(value.Length >> 8);
this.Data[i++] = (byte)value.Length;
if (value.Length > 0)
{
Buffer.BlockCopy(value.ToByteArray(), 0, this.Data, i, value.Length);
i += value.Length;
}
offset = this.WriteRecord(offset, value.ToByteArray(), IptcRecordNumber.Application, (byte)value.Tag);
}
}
private int WriteRecord(int offset, ReadOnlySpan<byte> recordData, IptcRecordNumber recordNumber, byte recordBinaryRepresentation)
{
Span<byte> data = this.Data.AsSpan(offset, 5);
data[0] = IptcTagMarkerByte;
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()
@ -298,6 +331,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
bool isValidRecordNumber = recordNumber is >= 1 and <= 9;
var tag = (IptcTag)this.Data[offset++];
bool isValidEntry = isValidTagMarker && isValidRecordNumber;
bool isApplicationRecord = recordNumber == (byte)IptcRecordNumber.Application;
uint byteCount = BinaryPrimitives.ReadUInt16BigEndian(this.Data.AsSpan(offset, 2));
offset += 2;
@ -307,9 +341,9 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
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);
this.values.Add(new IptcValue(tag, iptcData, false));
}
@ -317,5 +351,22 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
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>
public static Buffer2D<ulong> CalculateIntegralImage<TPixel>(this Image<TPixel> source)
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();
int endY = source.Height;
int endX = source.Width;
var interest = Rectangle.Intersect(bounds, source.Bounds());
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;
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<TPixel> sourceRow = sourceBuffer.DangerousGetRowSpan(0);
Span<TPixel> sourceRow = sourceBuffer.DangerousGetRowSpan(startY).Slice(startX, tempSpan.Length);
Span<ulong> destRow = intImage.DangerousGetRowSpan(0);
PixelOperations<TPixel>.Instance.ToL8(configuration, sourceRow, tempSpan);
// First row
for (int x = 0; x < endX; x++)
for (int x = 0; x < tempSpan.Length; x++)
{
sumX0 += tempSpan[x].PackedValue;
destRow[x] = sumX0;
@ -52,7 +86,7 @@ namespace SixLabors.ImageSharp.Processing
// All other rows
for (int y = 1; y < endY; y++)
{
sourceRow = sourceBuffer.DangerousGetRowSpan(y);
sourceRow = sourceBuffer.DangerousGetRowSpan(y + startY).Slice(startX, tempSpan.Length);
destRow = intImage.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToL8(configuration, sourceRow, tempSpan);
@ -62,7 +96,7 @@ namespace SixLabors.ImageSharp.Processing
destRow[0] = sumX0 + previousDestRow[0];
// Process all other colmns
for (int x = 1; x < endX; x++)
for (int x = 1; x < tempSpan.Length; x++)
{
sumX0 += tempSpan[x].PackedValue;
destRow[x] = sumX0 + previousDestRow[x];

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

@ -3,7 +3,6 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
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>
public AdaptiveThresholdProcessor(Configuration configuration, AdaptiveThresholdProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(configuration, source, sourceRectangle)
{
this.definition = definition;
}
=> this.definition = definition;
/// <inheritdoc/>
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;
TPixel upper = this.definition.Upper.ToPixel<TPixel>();
TPixel lower = this.definition.Lower.ToPixel<TPixel>();
float thresholdLimit = this.definition.ThresholdLimit;
int startY = intersect.Y;
int endY = intersect.Bottom;
int startX = intersect.X;
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);
// 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.Clamp(interest.Width / 16F, 0, 255);
Buffer2D<TPixel> sourceBuffer = source.PixelBuffer;
// Using pooled 2d buffer for integer image table and temp memory to hold Rgb24 converted pixel data.
using (Buffer2D<ulong> intImage = this.Configuration.MemoryAllocator.Allocate2D<ulong>(width, height))
{
Rgba32 rgb = default;
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);
}
using Buffer2D<ulong> intImage = source.CalculateIntegralImage(interest);
RowOperation operation = new(configuration, interest, source.PixelBuffer, intImage, upper, lower, thresholdLimit, clusterSize);
ParallelRowIterator.IterateRows<RowOperation, L8>(
configuration,
interest,
in operation);
}
private readonly struct RowOperation : IRowOperation
private readonly struct RowOperation : IRowOperation<L8>
{
private readonly Configuration configuration;
private readonly Rectangle bounds;
private readonly Buffer2D<TPixel> source;
private readonly Buffer2D<ulong> intImage;
@ -98,62 +60,58 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
private readonly TPixel lower;
private readonly float thresholdLimit;
private readonly int startX;
private readonly int endX;
private readonly int startY;
private readonly byte clusterSize;
[MethodImpl(InliningOptions.ShortMethod)]
public RowOperation(
Configuration configuration,
Rectangle bounds,
Buffer2D<TPixel> source,
Buffer2D<ulong> intImage,
TPixel upper,
TPixel lower,
float thresholdLimit,
byte clusterSize,
int startX,
int endX,
int startY)
byte clusterSize)
{
this.configuration = configuration;
this.bounds = bounds;
this.startX = bounds.X;
this.startY = bounds.Y;
this.source = source;
this.intImage = intImage;
this.upper = upper;
this.lower = lower;
this.thresholdLimit = thresholdLimit;
this.startX = startX;
this.endX = endX;
this.startY = startY;
this.clusterSize = clusterSize;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y)
public void Invoke(int y, Span<L8> span)
{
Rgba32 rgb = default;
Span<TPixel> pixelRow = this.source.DangerousGetRowSpan(y);
Span<TPixel> rowSpan = this.source.DangerousGetRowSpan(y).Slice(this.startX, span.Length);
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];
pixel.ToRgba32(ref rgb);
var x1 = Math.Max(x - this.startX - this.clusterSize + 1, 0);
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);
int x1 = Math.Clamp(x - this.clusterSize + 1, 0, maxX);
int x2 = Math.Min(x + this.clusterSize + 1, maxX);
int y1 = Math.Clamp(y - this.startY - this.clusterSize + 1, 0, maxY);
int y2 = Math.Min(y - this.startY + this.clusterSize + 1, maxY);
var 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);
uint count = (uint)((x2 - x1) * (y2 - y1));
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
{
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 BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Tests;
@ -24,7 +25,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
private void GenericBechmark()
{
this.preloadedImageStream.Position = 0;
using Image img = this.decoder.Decode(Configuration.Default, this.preloadedImageStream, default);
using Image img = this.decoder.Decode(DecoderOptions.Default, this.preloadedImageStream);
}
[GlobalSetup(Target = nameof(JpegBaselineInterleaved444))]
@ -64,7 +65,6 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
}
}
/*
BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19042.1348 (20H2/October2020Update)
Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -4,7 +4,7 @@
using System;
using System.IO;
using Microsoft.DotNet.RemoteExecutor;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
@ -75,11 +75,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
}
string providerDump = BasicSerializer.Serialize(provider);
RemoteExecutor.Invoke(
RunTest,
providerDump,
"Disco")
.Dispose();
RemoteExecutor.Invoke(RunTest, providerDump, "Disco").Dispose();
}
[Theory]
@ -98,11 +94,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecodeBitfields<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
[Theory]
@ -111,11 +105,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_Inverted<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
[Theory]
@ -124,11 +116,22 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_1Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, new SystemDrawingReferenceDecoder());
}
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider, new SystemDrawingReferenceDecoder());
}
[Theory]
[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]
@ -136,11 +139,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_4Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
[Theory]
@ -148,11 +149,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_8Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
[Theory]
@ -160,11 +159,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_16Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
[Theory]
@ -172,11 +169,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_32Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
[Theory]
@ -184,11 +179,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_32BitV4Header_Fast<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
[Theory]
@ -199,11 +192,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
where TPixel : unmanaged, IPixel<TPixel>
{
RleSkippedPixelHandling skippedPixelHandling = TestEnvironment.IsWindows ? RleSkippedPixelHandling.Black : RleSkippedPixelHandling.FirstColorOfPalette;
using (Image<TPixel> image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = skippedPixelHandling }))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
BmpDecoderOptions options = new() { RleSkippedPixelHandling = skippedPixelHandling };
using Image<TPixel> image = provider.GetImage(BmpDecoder, options);
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
[Theory]
@ -212,11 +205,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
where TPixel : unmanaged, IPixel<TPixel>
{
RleSkippedPixelHandling skippedPixelHandling = TestEnvironment.IsWindows ? RleSkippedPixelHandling.Black : RleSkippedPixelHandling.FirstColorOfPalette;
using (Image<TPixel> image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = skippedPixelHandling }))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
BmpDecoderOptions options = new() { RleSkippedPixelHandling = skippedPixelHandling };
using Image<TPixel> image = provider.GetImage(BmpDecoder, options);
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
[Theory]
@ -227,13 +220,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit_WithDelta_SystemDrawingRefDecoder<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = RleSkippedPixelHandling.Black }))
BmpDecoderOptions options = new() { RleSkippedPixelHandling = RleSkippedPixelHandling.Black };
using Image<TPixel> image = provider.GetImage(BmpDecoder, options);
image.DebugSave(provider);
if (TestEnvironment.IsWindows)
{
image.DebugSave(provider);
if (TestEnvironment.IsWindows)
{
image.CompareToOriginal(provider, new SystemDrawingReferenceDecoder());
}
image.CompareToOriginal(provider, new SystemDrawingReferenceDecoder());
}
}
@ -243,11 +235,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit_WithDelta_MagickRefDecoder<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = RleSkippedPixelHandling.FirstColorOfPalette }))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, new MagickReferenceDecoder());
}
BmpDecoderOptions options = new() { RleSkippedPixelHandling = RleSkippedPixelHandling.FirstColorOfPalette };
using Image<TPixel> image = provider.GetImage(BmpDecoder, options);
image.DebugSave(provider);
image.CompareToOriginal(provider, new MagickReferenceDecoder());
}
[Theory]
@ -263,11 +254,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
provider.LimitAllocatorBufferCapacity().InBytesSqrt(400);
}
using (Image<TPixel> image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = RleSkippedPixelHandling.FirstColorOfPalette }))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, new MagickReferenceDecoder());
}
BmpDecoderOptions options = new() { RleSkippedPixelHandling = RleSkippedPixelHandling.FirstColorOfPalette };
using Image<TPixel> image = provider.GetImage(BmpDecoder, options);
image.DebugSave(provider);
image.CompareToOriginal(provider, new MagickReferenceDecoder());
}
[Theory]
@ -285,13 +275,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
provider.LimitAllocatorBufferCapacity().InBytesSqrt(400);
}
using (Image<TPixel> image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = RleSkippedPixelHandling.Black }))
{
image.DebugSave(provider);
BmpDecoderOptions options = new() { RleSkippedPixelHandling = RleSkippedPixelHandling.Black };
using Image<TPixel> image = provider.GetImage(BmpDecoder, options);
image.DebugSave(provider);
// TODO: Neither System.Drawing nor MagickReferenceDecoder decode this file.
// image.CompareToOriginal(provider);
}
// Neither System.Drawing nor MagickReferenceDecoder decode this file.
// Compare to reference output instead.
image.CompareToReferenceOutput(provider, extension: "png");
}
[Theory]
@ -299,13 +289,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecodeAlphaBitfields<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
// TODO: Neither System.Drawing nor MagickReferenceDecoder decode this file.
// image.CompareToOriginal(provider);
}
// Neither System.Drawing nor MagickReferenceDecoder decode this file.
// Compare to reference output instead.
image.CompareToReferenceOutput(provider, extension: "png");
}
[Theory]
@ -313,11 +302,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecodeBitmap_WithAlphaChannel<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, new MagickReferenceDecoder());
}
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider, new MagickReferenceDecoder());
}
[Theory]
@ -325,17 +312,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecodeBitfields_WithUnusualBitmasks<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
// Choosing large tolerance of 6.1 here, because for some reason with the MagickReferenceDecoder the alpha channel
// seems to be wrong. This bitmap has an alpha channel of two bits. In many cases this alpha channel has a value of 3,
// which should be remapped to 255 for RGBA32, but the magick decoder has a value of 191 set.
// The total difference without the alpha channel is still: 0.0204%
// Exporting the image as PNG with GIMP yields to the same result as the ImageSharp implementation.
image.CompareToOriginal(provider, ImageComparer.TolerantPercentage(6.1f), new MagickReferenceDecoder());
}
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
// Choosing large tolerance of 6.1 here, because for some reason with the MagickReferenceDecoder the alpha channel
// seems to be wrong. This bitmap has an alpha channel of two bits. In many cases this alpha channel has a value of 3,
// which should be remapped to 255 for RGBA32, but the magick decoder has a value of 191 set.
// The total difference without the alpha channel is still: 0.0204%
// Exporting the image as PNG with GIMP yields to the same result as the ImageSharp implementation.
image.CompareToOriginal(provider, ImageComparer.TolerantPercentage(6.1f), new MagickReferenceDecoder());
}
[Theory]
@ -344,13 +329,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecodeBmpv2<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
// Do not validate. Reference files will fail validation.
image.CompareToOriginal(provider, new MagickReferenceDecoder(false));
}
// Do not validate. Reference files will fail validation.
image.CompareToOriginal(provider, new MagickReferenceDecoder(false));
}
[Theory]
@ -358,11 +341,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecodeBmpv3<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
[Theory]
@ -370,11 +351,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecodeLessThanFullPalette<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, new MagickReferenceDecoder());
}
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider, new MagickReferenceDecoder());
}
[Theory]
@ -383,13 +362,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecodeOversizedPalette<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
if (TestEnvironment.IsWindows)
{
image.DebugSave(provider);
if (TestEnvironment.IsWindows)
{
image.CompareToOriginal(provider);
}
image.CompareToOriginal(provider);
}
}
@ -397,39 +374,33 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
[WithFile(InvalidPaletteSize, PixelTypes.Rgba32)]
public void BmpDecoder_ThrowsInvalidImageContentException_OnInvalidPaletteSize<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
Assert.Throws<InvalidImageContentException>(() =>
=> Assert.Throws<InvalidImageContentException>(() =>
{
using (provider.GetImage(BmpDecoder))
{
}
});
}
[Theory]
[WithFile(Rgb24jpeg, PixelTypes.Rgba32)]
[WithFile(Rgb24png, PixelTypes.Rgba32)]
public void BmpDecoder_ThrowsNotSupportedException_OnUnsupportedBitmaps<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
Assert.Throws<NotSupportedException>(() =>
=> Assert.Throws<NotSupportedException>(() =>
{
using (provider.GetImage(BmpDecoder))
{
}
});
}
[Theory]
[WithFile(Rgb32h52AdobeV3, PixelTypes.Rgba32)]
public void BmpDecoder_CanDecodeAdobeBmpv3<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, new MagickReferenceDecoder());
}
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider, new MagickReferenceDecoder());
}
[Theory]
@ -437,11 +408,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecodeAdobeBmpv3_WithAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, new MagickReferenceDecoder());
}
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider, new MagickReferenceDecoder());
}
[Theory]
@ -449,11 +418,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecodeBmpv4<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
[Theory]
@ -462,11 +429,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecodeBmpv5<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
[Theory]
@ -474,11 +439,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_RespectsFileHeaderOffset<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
[Theory]
@ -486,11 +449,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
[Theory]
@ -498,11 +459,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode4BytePerEntryPalette<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
[Theory]
@ -521,12 +480,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void Identify_DetectsCorrectPixelType(string imagePath, int expectedPixelSize)
{
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
IImageInfo imageInfo = Image.Identify(stream);
Assert.NotNull(imageInfo);
Assert.Equal(expectedPixelSize, imageInfo.PixelType?.BitsPerPixel);
}
using var stream = new MemoryStream(testFile.Bytes, false);
IImageInfo imageInfo = Image.Identify(stream);
Assert.NotNull(imageInfo);
Assert.Equal(expectedPixelSize, imageInfo.PixelType?.BitsPerPixel);
}
[Theory]
@ -541,13 +498,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void Identify_DetectsCorrectWidthAndHeight(string imagePath, int expectedWidth, int expectedHeight)
{
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
IImageInfo imageInfo = Image.Identify(stream);
Assert.NotNull(imageInfo);
Assert.Equal(expectedWidth, imageInfo.Width);
Assert.Equal(expectedHeight, imageInfo.Height);
}
using var stream = new MemoryStream(testFile.Bytes, false);
IImageInfo imageInfo = Image.Identify(stream);
Assert.NotNull(imageInfo);
Assert.Equal(expectedWidth, imageInfo.Width);
Assert.Equal(expectedHeight, imageInfo.Height);
}
[Theory]
@ -555,17 +510,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
var decoder = new BmpDecoder();
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, stream, default))
{
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
}
using var stream = new MemoryStream(testFile.Bytes, false);
var decoder = new BmpDecoder();
using Image<Rgba32> image = decoder.Decode<Rgba32>(DecoderOptions.Default, stream);
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
[Theory]
@ -573,13 +524,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_Os2v2XShortHeader<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
// TODO: Neither System.Drawing or MagickReferenceDecoder can correctly decode this file.
// image.CompareToOriginal(provider);
}
// Neither System.Drawing or MagickReferenceDecoder can correctly decode this file.
// Compare to reference output instead.
image.CompareToReferenceOutput(provider, extension: "png");
}
[Theory]
@ -587,15 +537,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_Os2v2Header<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
// TODO: System.Drawing can not decode this image. MagickReferenceDecoder can decode it,
// but i think incorrectly. I have loaded the image with GIMP and exported as PNG.
// The results are the same as the image sharp implementation.
// image.CompareToOriginal(provider, new MagickReferenceDecoder());
}
// System.Drawing can not decode this image. MagickReferenceDecoder can decode it,
// Compare to reference output instead.
image.CompareToReferenceOutput(provider, extension: "png");
}
[Theory]
@ -611,13 +558,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_Os2BitmapArray<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
// TODO: Neither System.Drawing or MagickReferenceDecoder can correctly decode this file.
// image.CompareToOriginal(provider);
}
// Neither System.Drawing or MagickReferenceDecoder can correctly decode this file.
// 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")]
public class BmpEncoderTests
{
private static BmpDecoder BmpDecoder => new();
private static BmpEncoder BmpEncoder => new();
public static readonly TheoryData<BmpBitsPerPixel> BitsPerPixel =
@ -42,6 +44,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
new()
{
{ Bit1, BmpBitsPerPixel.Pixel1 },
{ Bit2, BmpBitsPerPixel.Pixel2 },
{ Bit4, BmpBitsPerPixel.Pixel4 },
{ Bit8, BmpBitsPerPixel.Pixel8 },
{ 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)
{
var testFile = TestFile.Create(imagePath);
using (Image<Rgba32> input = testFile.CreateRgba32Image())
{
using (var memStream = new MemoryStream())
{
input.Save(memStream, BmpEncoder);
memStream.Position = 0;
using (var output = Image.Load<Rgba32>(memStream))
{
ImageMetadata meta = output.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
}
}
using Image<Rgba32> input = testFile.CreateRgba32Image();
using var memStream = new MemoryStream();
input.Save(memStream, BmpEncoder);
memStream.Position = 0;
using var output = Image.Load<Rgba32>(memStream);
ImageMetadata meta = output.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
[Theory]
@ -77,21 +74,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void Encode_PreserveBitsPerPixel(string imagePath, BmpBitsPerPixel bmpBitsPerPixel)
{
var testFile = TestFile.Create(imagePath);
using (Image<Rgba32> input = testFile.CreateRgba32Image())
{
using (var memStream = new MemoryStream())
{
input.Save(memStream, BmpEncoder);
memStream.Position = 0;
using (var output = Image.Load<Rgba32>(memStream))
{
BmpMetadata meta = output.Metadata.GetBmpMetadata();
Assert.Equal(bmpBitsPerPixel, meta.BitsPerPixel);
}
}
}
using Image<Rgba32> input = testFile.CreateRgba32Image();
using var memStream = new MemoryStream();
input.Save(memStream, BmpEncoder);
memStream.Position = 0;
using var output = Image.Load<Rgba32>(memStream);
BmpMetadata meta = output.Metadata.GetBmpMetadata();
Assert.Equal(bmpBitsPerPixel, meta.BitsPerPixel);
}
[Theory]
@ -203,6 +194,50 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
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]
[WithFile(Bit1, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel1)]
public void Encode_1Bit_WithV3Header_Works<TPixel>(
@ -236,28 +271,26 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
return;
}
using (Image<TPixel> image = provider.GetImage())
using Image<TPixel> image = provider.GetImage();
var encoder = new BmpEncoder
{
var encoder = new BmpEncoder
{
BitsPerPixel = BmpBitsPerPixel.Pixel8,
Quantizer = new WuQuantizer()
};
string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "bmp", encoder, appendPixelTypeToFileName: false);
// Use the default decoder to test our encoded image. This verifies the content.
// We do not verify the reference image though as some are invalid.
IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile);
using (var referenceImage = Image.Load<TPixel>(actualOutputFile, referenceDecoder))
{
referenceImage.CompareToReferenceOutput(
ImageComparer.TolerantPercentage(0.01f),
provider,
extension: "bmp",
appendPixelTypeToFileName: false,
decoder: new MagickReferenceDecoder(false));
}
}
BitsPerPixel = BmpBitsPerPixel.Pixel8,
Quantizer = new WuQuantizer()
};
string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "bmp", encoder, appendPixelTypeToFileName: false);
// Use the default decoder to test our encoded image. This verifies the content.
// We do not verify the reference image though as some are invalid.
IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile);
using FileStream stream = File.OpenRead(actualOutputFile);
using Image<TPixel> referenceImage = referenceDecoder.Decode<TPixel>(DecoderOptions.Default, stream, default);
referenceImage.CompareToReferenceOutput(
ImageComparer.TolerantPercentage(0.01f),
provider,
extension: "bmp",
appendPixelTypeToFileName: false,
decoder: new MagickReferenceDecoder(false));
}
[Theory]
@ -270,28 +303,25 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
return;
}
using (Image<TPixel> image = provider.GetImage())
using Image<TPixel> image = provider.GetImage();
var encoder = new BmpEncoder
{
var encoder = new BmpEncoder
{
BitsPerPixel = BmpBitsPerPixel.Pixel8,
Quantizer = new OctreeQuantizer()
};
string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "bmp", encoder, appendPixelTypeToFileName: false);
// Use the default decoder to test our encoded image. This verifies the content.
// We do not verify the reference image though as some are invalid.
IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile);
using (var referenceImage = Image.Load<TPixel>(actualOutputFile, referenceDecoder))
{
referenceImage.CompareToReferenceOutput(
ImageComparer.TolerantPercentage(0.01f),
provider,
extension: "bmp",
appendPixelTypeToFileName: false,
decoder: new MagickReferenceDecoder(false));
}
}
BitsPerPixel = BmpBitsPerPixel.Pixel8,
Quantizer = new OctreeQuantizer()
};
string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "bmp", encoder, appendPixelTypeToFileName: false);
// Use the default decoder to test our encoded image. This verifies the content.
// We do not verify the reference image though as some are invalid.
IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile);
using FileStream stream = File.OpenRead(actualOutputFile);
using Image<TPixel> referenceImage = referenceDecoder.Decode<TPixel>(DecoderOptions.Default, stream, default);
referenceImage.CompareToReferenceOutput(
ImageComparer.TolerantPercentage(0.01f),
provider,
extension: "bmp",
appendPixelTypeToFileName: false,
decoder: new MagickReferenceDecoder(false));
}
[Theory]
@ -305,26 +335,20 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void Encode_PreservesColorProfile<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> input = provider.GetImage(new BmpDecoder()))
{
ImageSharp.Metadata.Profiles.Icc.IccProfile expectedProfile = input.Metadata.IccProfile;
byte[] expectedProfileBytes = expectedProfile.ToByteArray();
using (var memStream = new MemoryStream())
{
input.Save(memStream, new BmpEncoder());
memStream.Position = 0;
using (var output = Image.Load<Rgba32>(memStream))
{
ImageSharp.Metadata.Profiles.Icc.IccProfile actualProfile = output.Metadata.IccProfile;
byte[] actualProfileBytes = actualProfile.ToByteArray();
Assert.NotNull(actualProfile);
Assert.Equal(expectedProfileBytes, actualProfileBytes);
}
}
}
using Image<TPixel> input = provider.GetImage(new BmpDecoder(), new());
ImageSharp.Metadata.Profiles.Icc.IccProfile expectedProfile = input.Metadata.IccProfile;
byte[] expectedProfileBytes = expectedProfile.ToByteArray();
using var memStream = new MemoryStream();
input.Save(memStream, new BmpEncoder());
memStream.Position = 0;
using var output = Image.Load<Rgba32>(memStream);
ImageSharp.Metadata.Profiles.Icc.IccProfile actualProfile = output.Metadata.IccProfile;
byte[] actualProfileBytes = actualProfile.ToByteArray();
Assert.NotNull(actualProfile);
Assert.Equal(expectedProfileBytes, actualProfileBytes);
}
[Theory]
@ -357,27 +381,27 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
BmpBitsPerPixel bitsPerPixel,
bool supportTransparency = true, // if set to true, will write a V4 header, otherwise a V3 header.
IQuantizer quantizer = null,
ImageComparer customComparer = null)
ImageComparer customComparer = null,
IImageDecoder referenceDecoder = null)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
using Image<TPixel> image = provider.GetImage();
// There is no alpha in bmp with less then 32 bits per pixels, so the reference image will be made opaque.
if (bitsPerPixel != BmpBitsPerPixel.Pixel32)
{
// There is no alpha in bmp with less then 32 bits per pixels, so the reference image will be made opaque.
if (bitsPerPixel != BmpBitsPerPixel.Pixel32)
{
image.Mutate(c => c.MakeOpaque());
}
var encoder = new BmpEncoder
{
BitsPerPixel = bitsPerPixel,
SupportTransparency = supportTransparency,
Quantizer = quantizer ?? KnownQuantizers.Octree
};
// Does DebugSave & load reference CompareToReferenceInput():
image.VerifyEncoder(provider, "bmp", bitsPerPixel, encoder, customComparer);
image.Mutate(c => c.MakeOpaque());
}
var encoder = new BmpEncoder
{
BitsPerPixel = bitsPerPixel,
SupportTransparency = supportTransparency,
Quantizer = quantizer ?? KnownQuantizers.Octree
};
// Does DebugSave & load reference CompareToReferenceInput():
image.VerifyEncoder(provider, "bmp", bitsPerPixel, encoder, customComparer);
}
}
}

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

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

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

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

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

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

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

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

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

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

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

@ -38,8 +38,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
// arrange
using var input = new Image<Rgba32>(1, 1);
input.Metadata.IptcProfile = new IptcProfile();
input.Metadata.IptcProfile.SetValue(IptcTag.Byline, "unit_test");
var expectedProfile = new IptcProfile();
expectedProfile.SetValue(IptcTag.Country, "ESPAÑA");
expectedProfile.SetValue(IptcTag.City, "unit-test-city");
input.Metadata.IptcProfile = expectedProfile;
// act
using var memStream = new MemoryStream();
@ -50,7 +52,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
using var output = Image.Load<Rgba32>(memStream);
IptcProfile actual = output.Metadata.IptcProfile;
Assert.NotNull(actual);
IEnumerable<IptcValue> values = input.Metadata.IptcProfile.Values;
IEnumerable<IptcValue> values = expectedProfile.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
byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes;
JpegDecoderOptions option = new();
using var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder());
using var decoder = new JpegDecoderCore(option);
using var ms = new MemoryStream(sourceBytes);
using var bufferedStream = new BufferedReadStream(Configuration.Default, ms);
@ -78,8 +79,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
// Calculating data from ImageSharp
byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes;
JpegDecoderOptions options = new();
using var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder());
using var decoder = new JpegDecoderCore(options);
using var ms = new MemoryStream(sourceBytes);
using var bufferedStream = new BufferedReadStream(Configuration.Default, ms);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Loading…
Cancel
Save