Browse Source

Merge remote-tracking branch 'origin/main' into bp/webpanimation

# Conflicts:
#	src/ImageSharp/Formats/Webp/WebpDecoder.cs
pull/1985/head
Brian Popow 4 years ago
parent
commit
5a1ad2f68a
  1. 2
      .github/ISSUE_TEMPLATE/config.yml
  2. 5
      README.md
  3. 3
      src/ImageSharp/Advanced/AotCompilerTools.cs
  4. 38
      src/ImageSharp/Formats/Bmp/BmpDecoder.cs
  5. 38
      src/ImageSharp/Formats/Gif/GifDecoder.cs
  6. 25
      src/ImageSharp/Formats/IImageDecoder.cs
  7. 11
      src/ImageSharp/Formats/IImageInfoDetector.cs
  8. 131
      src/ImageSharp/Formats/ImageDecoderUtilities.cs
  9. 43
      src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs
  10. 55
      src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbForwardConverter{TPixel}.cs
  11. 4
      src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs
  12. 43
      src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
  13. 19
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  14. 37
      src/ImageSharp/Formats/Pbm/PbmDecoder.cs
  15. 110
      src/ImageSharp/Formats/Png/PngDecoder.cs
  16. 38
      src/ImageSharp/Formats/Tga/TgaDecoder.cs
  17. 222
      src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs
  18. 4
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs
  19. 4
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs
  20. 77
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16161616TiffColor{TPixel}.cs
  21. 75
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16PlanarTiffColor{TPixel}.cs
  22. 90
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24242424TiffColor{TPixel}.cs
  23. 85
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24PlanarTiffColor{TPixel}.cs
  24. 79
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32323232TiffColor{TPixel}.cs
  25. 74
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32PlanarTiffColor{TPixel}.cs
  26. 39
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba8888TiffColor{TPixel}.cs
  27. 97
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaFloat32323232TiffColor{TPixel}.cs
  28. 87
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaPlanarTiffColor{TPixel}.cs
  29. 72
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaTiffColor{TPixel}.cs
  30. 189
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs
  31. 100
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs
  32. 2
      src/ImageSharp/Formats/Tiff/README.md
  33. 38
      src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs
  34. 36
      src/ImageSharp/Formats/Tiff/TiffDecoder.cs
  35. 22
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  36. 60
      src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
  37. 27
      src/ImageSharp/Formats/Tiff/TiffExtraSampleType.cs
  38. 33
      src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs
  39. 49
      src/ImageSharp/Formats/Webp/WebpDecoder.cs
  40. 56
      src/ImageSharp/IO/ChunkedMemoryStream.cs
  41. 118
      src/ImageSharp/Image.Decode.cs
  42. 10
      src/ImageSharp/Image.FromFile.cs
  43. 36
      src/ImageSharp/Image.FromStream.cs
  44. 6
      src/ImageSharp/ImageFrame{TPixel}.cs
  45. 2
      src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.String.cs
  46. 2
      src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValue.cs
  47. 4
      src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/RgbaVector.PixelOperations.cs
  48. 24
      src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs
  49. 6
      src/ImageSharp/Processing/Extensions/Transforms/PadExtensions.cs
  50. 2
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs
  51. 15
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs
  52. 27
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs
  53. 2
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs
  54. 134
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs
  55. 112
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegComparison.cs
  56. 89
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegFeatures.cs
  57. 34
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegMultiple.cs
  58. 2
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs
  59. 48
      tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs
  60. 24
      tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs
  61. 5
      tests/ImageSharp.Tests.ruleset
  62. 2
      tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
  63. 2
      tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs
  64. 10
      tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs
  65. 28
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs
  66. 34
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
  67. 2
      tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs
  68. 2
      tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
  69. 8
      tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs
  70. 141
      tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
  71. 80
      tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs
  72. 11
      tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs
  73. 4
      tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_PassLocalConfiguration.cs
  74. 4
      tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_PassLocalConfiguration.cs
  75. 61
      tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs
  76. 16
      tests/ImageSharp.Tests/PixelFormats/RgbaVectorTests.cs
  77. 6
      tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs
  78. 23
      tests/ImageSharp.Tests/TestFormat.cs
  79. 37
      tests/ImageSharp.Tests/TestImages.cs
  80. 16
      tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs
  81. 18
      tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs
  82. 18
      tests/ImageSharp.Tests/TestUtilities/TestPixel.cs
  83. 41
      tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs
  84. 4
      tests/Images/External/ReferenceOutput/ResizeTests/ResizeWithBoxPadMode_CalliphoraPartial.png
  85. 4
      tests/Images/External/ReferenceOutput/ResizeTests/ResizeWithPadMode_CalliphoraPartial.png
  86. 3
      tests/Images/Input/Jpg/issues/Issue2057-App1Parsing.jpg
  87. 3
      tests/Images/Input/Jpg/issues/issue-2056-exif-null-array.jpg
  88. 3
      tests/Images/Input/Tiff/RgbaUnassociatedAlpha10bit_lsb.tiff
  89. 3
      tests/Images/Input/Tiff/RgbaUnassociatedAlpha10bit_msb.tiff
  90. 3
      tests/Images/Input/Tiff/RgbaUnassociatedAlpha12bit_lsb.tiff
  91. 3
      tests/Images/Input/Tiff/RgbaUnassociatedAlpha12bit_msb.tiff
  92. 3
      tests/Images/Input/Tiff/RgbaUnassociatedAlpha14bit_lsb.tiff
  93. 3
      tests/Images/Input/Tiff/RgbaUnassociatedAlpha14bit_msb.tiff
  94. 3
      tests/Images/Input/Tiff/RgbaUnassociatedAlpha16bit_lsb.tiff
  95. 3
      tests/Images/Input/Tiff/RgbaUnassociatedAlpha16bit_msb.tiff
  96. 3
      tests/Images/Input/Tiff/RgbaUnassociatedAlpha24bit_lsb.tiff
  97. 3
      tests/Images/Input/Tiff/RgbaUnassociatedAlpha24bit_msb.tiff
  98. 3
      tests/Images/Input/Tiff/RgbaUnassociatedAlpha2bit.tiff
  99. 3
      tests/Images/Input/Tiff/RgbaUnassociatedAlpha32bit_lsb.tiff
  100. 3
      tests/Images/Input/Tiff/RgbaUnassociatedAlpha32bit_msb.tiff

2
.github/ISSUE_TEMPLATE/config.yml

@ -1,5 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Feature Request
url: https://github.com/SixLabors/ImageSharp/discussions?discussions_q=category%3AIdeas
url: https://github.com/SixLabors/ImageSharp/discussions/categories/ideas
about: Share ideas for new features for this project.

5
README.md

@ -26,7 +26,7 @@ Built against [.NET Standard 2.0](https://docs.microsoft.com/en-us/dotnet/standa
## License
- ImageSharp is licensed under the [Apache License, Version 2.0](https://opensource.org/licenses/Apache-2.0)
- An alternative Commercial Support License can be purchased **for projects and applications requiring support**.
- An alternative Six Labors License can be purchased **for projects and applications requiring developer support**.
Please visit https://sixlabors.com/pricing for details.
## Support Six Labors
@ -43,7 +43,8 @@ Support the efforts of the development of the Six Labors projects.
## Questions
- Do you have questions? We are happy to help! Please [join our Discussions Forum](https://github.com/SixLabors/ImageSharp/discussions/category_choices), or ask them on [Stack Overflow](https://stackoverflow.com) using the `ImageSharp` tag. Please do not open issues for questions.
- Do you have questions? We are happy to help! Simply purchase a [Six Labors License](https://sixlabors.com/pricing) for developer support. Please do not open issues for questions or misuse our [Discussions Forum](https://github.com/SixLabors/ImageSharp/discussions).
- For feature ideas please [join our Discussions Forum](https://github.com/SixLabors/ImageSharp/discussions/categories/ideas) and we'll be happy to discuss.
- Please read our [Contribution Guide](https://github.com/SixLabors/ImageSharp/blob/main/.github/CONTRIBUTING.md) before opening issues or pull requests!
## Code of Conduct

3
src/ImageSharp/Advanced/AotCompilerTools.cs

@ -287,8 +287,7 @@ namespace SixLabors.ImageSharp.Advanced
where TPixel : unmanaged, IPixel<TPixel>
where TDecoder : class, IImageDecoder
{
default(TDecoder).Decode<TPixel>(default, default);
default(TDecoder).DecodeAsync<TPixel>(default, default, default);
default(TDecoder).Decode<TPixel>(default, default, default);
}
/// <summary>

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

@ -3,9 +3,6 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Bmp
@ -31,48 +28,25 @@ namespace SixLabors.ImageSharp.Formats.Bmp
public RleSkippedPixelHandling RleSkippedPixelHandling { get; set; } = RleSkippedPixelHandling.Black;
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(stream, nameof(stream));
var decoder = new BmpDecoderCore(configuration, this);
return decoder.Decode<TPixel>(configuration, stream);
return decoder.Decode<TPixel>(configuration, stream, cancellationToken);
}
/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream)
=> this.Decode<Rgba32>(configuration, stream);
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>(configuration, stream, cancellationToken);
/// <inheritdoc/>
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(stream, nameof(stream));
var decoder = new BmpDecoderCore(configuration, this);
return decoder.DecodeAsync<TPixel>(configuration, stream, cancellationToken);
}
/// <inheritdoc />
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken)
.ConfigureAwait(false);
/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream)
{
Guard.NotNull(stream, nameof(stream));
return new BmpDecoderCore(configuration, this).Identify(configuration, stream);
}
/// <inheritdoc/>
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(stream, nameof(stream));
return new BmpDecoderCore(configuration, this).IdentifyAsync(configuration, stream, cancellationToken);
return new BmpDecoderCore(configuration, this).Identify(configuration, stream, cancellationToken);
}
}
}

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

@ -3,8 +3,6 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
@ -26,48 +24,24 @@ namespace SixLabors.ImageSharp.Formats.Gif
public FrameDecodingMode DecodingMode { get; set; } = FrameDecodingMode.All;
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
var decoder = new GifDecoderCore(configuration, this);
return decoder.Decode<TPixel>(configuration, stream);
return decoder.Decode<TPixel>(configuration, stream, cancellationToken);
}
/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream)
=> this.Decode<Rgba32>(configuration, stream);
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>(configuration, stream, cancellationToken);
/// <inheritdoc/>
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
var decoder = new GifDecoderCore(configuration, this);
return decoder.DecodeAsync<TPixel>(configuration, stream, cancellationToken);
}
/// <inheritdoc />
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken)
.ConfigureAwait(false);
/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream)
{
Guard.NotNull(stream, nameof(stream));
var decoder = new GifDecoderCore(configuration, this);
using var bufferedStream = new BufferedReadStream(configuration, stream);
return decoder.Identify(bufferedStream, default);
}
/// <inheritdoc/>
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(stream, nameof(stream));
var decoder = new GifDecoderCore(configuration, this);
return decoder.IdentifyAsync(configuration, stream, cancellationToken);
return decoder.Identify(configuration, stream, cancellationToken);
}
}
}

25
src/ImageSharp/Formats/IImageDecoder.cs

@ -3,7 +3,6 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats
@ -13,26 +12,6 @@ namespace SixLabors.ImageSharp.Formats
/// </summary>
public interface IImageDecoder
{
/// <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="configuration">The configuration for the image.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</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)
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>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <returns>The <see cref="Image"/>.</returns>
// TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110)
Image Decode(Configuration configuration, Stream stream);
/// <summary>
/// Decodes the image from the specified stream to an <see cref="Image{TPixel}"/> of a specific pixel type.
/// </summary>
@ -42,7 +21,7 @@ namespace SixLabors.ImageSharp.Formats
/// <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)
Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>;
/// <summary>
@ -53,6 +32,6 @@ namespace SixLabors.ImageSharp.Formats
/// <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)
Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken);
Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken);
}
}

11
src/ImageSharp/Formats/IImageInfoDetector.cs

@ -3,7 +3,6 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace SixLabors.ImageSharp.Formats
{
@ -12,14 +11,6 @@ namespace SixLabors.ImageSharp.Formats
/// </summary>
public interface IImageInfoDetector
{
/// <summary>
/// Reads the raw image information from the specified stream.
/// </summary>
/// <param name="configuration">The configuration for the image.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <returns>The <see cref="PixelTypeInfo"/> object</returns>
IImageInfo Identify(Configuration configuration, Stream stream);
/// <summary>
/// Reads the raw image information from the specified stream.
/// </summary>
@ -27,6 +18,6 @@ namespace SixLabors.ImageSharp.Formats
/// <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>
Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken);
IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken);
}
}

131
src/ImageSharp/Formats/ImageDecoderUtilities.cs

@ -4,7 +4,6 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -13,153 +12,45 @@ namespace SixLabors.ImageSharp.Formats
{
internal static class ImageDecoderUtilities
{
/// <summary>
/// Reads the raw image information from the specified stream.
/// </summary>
/// <param name="decoder">The decoder.</param>
/// /// <param name="configuration">The configuration for the image.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException"><paramref name="stream"/> is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task<IImageInfo> IdentifyAsync(
this IImageDecoderInternals decoder,
Configuration configuration,
Stream stream,
CancellationToken cancellationToken)
=> decoder.IdentifyAsync(configuration, stream, DefaultLargeImageExceptionFactory, cancellationToken);
/// <summary>
/// Reads the raw image information from the specified stream.
/// </summary>
/// <param name="decoder">The decoder.</param>
/// <param name="configuration">The configuration for the image.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="tooLargeImageExceptionFactory">Factory method to handle <see cref="InvalidMemoryOperationException"/> as <see cref="InvalidImageContentException"/>.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException"><paramref name="stream"/> is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task<IImageInfo> IdentifyAsync(
public static IImageInfo Identify(
this IImageDecoderInternals decoder,
Configuration configuration,
Stream stream,
Func<InvalidMemoryOperationException, Size, InvalidImageContentException> tooLargeImageExceptionFactory,
CancellationToken cancellationToken)
{
using var bufferedReadStream = new BufferedReadStream(configuration, stream);
try
{
using var bufferedReadStream = new BufferedReadStream(configuration, stream);
IImageInfo imageInfo = decoder.Identify(bufferedReadStream, cancellationToken);
return Task.FromResult(imageInfo);
return decoder.Identify(bufferedReadStream, cancellationToken);
}
catch (InvalidMemoryOperationException ex)
{
InvalidImageContentException invalidImageContentException = tooLargeImageExceptionFactory(ex, decoder.Dimensions);
return Task.FromException<IImageInfo>(invalidImageContentException);
}
catch (OperationCanceledException)
{
return Task.FromCanceled<IImageInfo>(cancellationToken);
}
catch (Exception ex)
{
return Task.FromException<IImageInfo>(ex);
throw new InvalidImageContentException(decoder.Dimensions, ex);
}
}
/// <summary>
/// Decodes the image from the specified stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="decoder">The decoder.</param>
/// <param name="configuration">The configuration for the image.</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"/> representing the asynchronous operation.</returns>
public static Task<Image<TPixel>> DecodeAsync<TPixel>(
this IImageDecoderInternals decoder,
Configuration configuration,
Stream stream,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> =>
decoder.DecodeAsync<TPixel>(
configuration,
stream,
DefaultLargeImageExceptionFactory,
cancellationToken);
/// <summary>
/// Decodes the image from the specified stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="decoder">The decoder.</param>
/// <param name="configuration">The configuration for the image.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="largeImageExceptionFactory">Factory method to handle <see cref="InvalidMemoryOperationException"/> as <see cref="InvalidImageContentException"/>.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task<Image<TPixel>> DecodeAsync<TPixel>(
public static Image<TPixel> Decode<TPixel>(
this IImageDecoderInternals decoder,
Configuration configuration,
Stream stream,
Func<InvalidMemoryOperationException, Size, InvalidImageContentException> largeImageExceptionFactory,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
try
{
using var bufferedReadStream = new BufferedReadStream(configuration, stream);
Image<TPixel> image = decoder.Decode<TPixel>(bufferedReadStream, cancellationToken);
return Task.FromResult(image);
}
catch (InvalidMemoryOperationException ex)
{
InvalidImageContentException invalidImageContentException = largeImageExceptionFactory(ex, decoder.Dimensions);
return Task.FromException<Image<TPixel>>(invalidImageContentException);
}
catch (OperationCanceledException)
{
return Task.FromCanceled<Image<TPixel>>(cancellationToken);
}
catch (Exception ex)
{
return Task.FromException<Image<TPixel>>(ex);
}
}
public static IImageInfo Identify(
this IImageDecoderInternals decoder,
Configuration configuration,
Stream stream)
{
using var bufferedReadStream = new BufferedReadStream(configuration, stream);
try
{
return decoder.Identify(bufferedReadStream, default);
}
catch (InvalidMemoryOperationException ex)
{
throw new InvalidImageContentException(decoder.Dimensions, ex);
}
}
public static Image<TPixel> Decode<TPixel>(this IImageDecoderInternals decoder, Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
=> decoder.Decode<TPixel>(configuration, stream, DefaultLargeImageExceptionFactory);
=> decoder.Decode<TPixel>(configuration, stream, DefaultLargeImageExceptionFactory, cancellationToken);
public static Image<TPixel> Decode<TPixel>(
this IImageDecoderInternals decoder,
Configuration configuration,
Stream stream,
Func<InvalidMemoryOperationException, Size, InvalidImageContentException> largeImageExceptionFactory)
Func<InvalidMemoryOperationException, Size, InvalidImageContentException> largeImageExceptionFactory,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
using var bufferedReadStream = new BufferedReadStream(configuration, stream);
try
{
return decoder.Decode<TPixel>(bufferedReadStream, default);
return decoder.Decode<TPixel>(bufferedReadStream, cancellationToken);
}
catch (InvalidMemoryOperationException ex)
{
@ -170,6 +61,6 @@ namespace SixLabors.ImageSharp.Formats
private static InvalidImageContentException DefaultLargeImageExceptionFactory(
InvalidMemoryOperationException memoryOperationException,
Size dimensions) =>
new InvalidImageContentException(dimensions, memoryOperationException);
new(dimensions, memoryOperationException);
}
}

43
src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs

@ -2,8 +2,13 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
#endif
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
@ -74,6 +79,44 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
ref Block8x8F yBlock = ref this.Y;
ref L8 l8Start = ref MemoryMarshal.GetReference(this.l8Span);
if (RgbToYCbCrConverterVectorized.IsSupported)
{
ConvertAvx(ref l8Start, ref yBlock);
}
else
{
ConvertScalar(ref l8Start, ref yBlock);
}
}
/// <summary>
/// Converts 8x8 L8 pixel matrix to 8x8 Block of floats using Avx2 Intrinsics.
/// </summary>
/// <param name="l8Start">Start of span of L8 pixels with size of 64</param>
/// <param name="yBlock">8x8 destination matrix of Luminance(Y) converted data</param>
private static void ConvertAvx(ref L8 l8Start, ref Block8x8F yBlock)
{
Debug.Assert(RgbToYCbCrConverterVectorized.IsSupported, "AVX2 is required to run this converter");
#if SUPPORTS_RUNTIME_INTRINSICS
ref Vector128<byte> l8ByteSpan = ref Unsafe.As<L8, Vector128<byte>>(ref l8Start);
ref Vector256<float> destRef = ref yBlock.V0;
const int bytesPerL8Stride = 8;
for (nint i = 0; i < 8; i++)
{
Unsafe.Add(ref destRef, i) = Avx2.ConvertToVector256Single(Avx2.ConvertToVector256Int32(Unsafe.AddByteOffset(ref l8ByteSpan, bytesPerL8Stride * i)));
}
#endif
}
/// <summary>
/// Converts 8x8 L8 pixel matrix to 8x8 Block of floats.
/// </summary>
/// <param name="l8Start">Start of span of L8 pixels with size of 64</param>
/// <param name="yBlock">8x8 destination matrix of Luminance(Y) converted data</param>
private static void ConvertScalar(ref L8 l8Start, ref Block8x8F yBlock)
{
for (int i = 0; i < Block8x8F.Size; i++)
{
ref L8 c = ref Unsafe.Add(ref l8Start, i);

55
src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbForwardConverter{TPixel}.cs

@ -2,8 +2,13 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
#endif
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
@ -94,10 +99,56 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
ref Block8x8F greenBlock = ref this.G;
ref Block8x8F blueBlock = ref this.B;
CopyToBlock(this.rgbSpan, ref redBlock, ref greenBlock, ref blueBlock);
if (RgbToYCbCrConverterVectorized.IsSupported)
{
ConvertAvx(this.rgbSpan, ref redBlock, ref greenBlock, ref blueBlock);
}
else
{
ConvertScalar(this.rgbSpan, ref redBlock, ref greenBlock, ref blueBlock);
}
}
/// <summary>
/// Converts 8x8 RGB24 pixel matrix to 8x8 Block of floats using Avx2 Intrinsics.
/// </summary>
/// <param name="rgbSpan">Span of Rgb24 pixels with size of 64</param>
/// <param name="rBlock">8x8 destination matrix of Red converted data</param>
/// <param name="gBlock">8x8 destination matrix of Blue converted data</param>
/// <param name="bBlock">8x8 destination matrix of Green converted data</param>
private static void ConvertAvx(Span<Rgb24> rgbSpan, ref Block8x8F rBlock, ref Block8x8F gBlock, ref Block8x8F bBlock)
{
Debug.Assert(RgbToYCbCrConverterVectorized.IsSupported, "AVX2 is required to run this converter");
#if SUPPORTS_RUNTIME_INTRINSICS
ref Vector256<byte> rgbByteSpan = ref Unsafe.As<Rgb24, Vector256<byte>>(ref MemoryMarshal.GetReference(rgbSpan));
ref Vector256<float> redRef = ref rBlock.V0;
ref Vector256<float> greenRef = ref gBlock.V0;
ref Vector256<float> blueRef = ref bBlock.V0;
var zero = Vector256.Create(0).AsByte();
var extractToLanesMask = Unsafe.As<byte, Vector256<uint>>(ref MemoryMarshal.GetReference(RgbToYCbCrConverterVectorized.MoveFirst24BytesToSeparateLanes));
var extractRgbMask = Unsafe.As<byte, Vector256<byte>>(ref MemoryMarshal.GetReference(RgbToYCbCrConverterVectorized.ExtractRgb));
Vector256<byte> rgb, rg, bx;
const int bytesPerRgbStride = 24;
for (nint i = 0; i < 8; i++)
{
rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref rgbByteSpan, bytesPerRgbStride * i).AsUInt32(), extractToLanesMask).AsByte();
rgb = Avx2.Shuffle(rgb, extractRgbMask);
rg = Avx2.UnpackLow(rgb, zero);
bx = Avx2.UnpackHigh(rgb, zero);
Unsafe.Add(ref redRef, i) = Avx.ConvertToVector256Single(Avx2.UnpackLow(rg, zero).AsInt32());
Unsafe.Add(ref greenRef, i) = Avx.ConvertToVector256Single(Avx2.UnpackHigh(rg, zero).AsInt32());
Unsafe.Add(ref blueRef, i) = Avx.ConvertToVector256Single(Avx2.UnpackLow(bx, zero).AsInt32());
}
#endif
}
private static void CopyToBlock(Span<Rgb24> rgbSpan, ref Block8x8F redBlock, ref Block8x8F greenBlock, ref Block8x8F blueBlock)
private static void ConvertScalar(Span<Rgb24> rgbSpan, ref Block8x8F redBlock, ref Block8x8F greenBlock, ref Block8x8F blueBlock)
{
ref Rgb24 rgbStart = ref MemoryMarshal.GetReference(rgbSpan);

4
src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs

@ -60,13 +60,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
#if SUPPORTS_RUNTIME_INTRINSICS
private static ReadOnlySpan<byte> MoveFirst24BytesToSeparateLanes => new byte[]
internal static ReadOnlySpan<byte> MoveFirst24BytesToSeparateLanes => new byte[]
{
0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 6, 0, 0, 0,
3, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 7, 0, 0, 0
};
private static ReadOnlySpan<byte> ExtractRgb => new byte[]
internal static ReadOnlySpan<byte> ExtractRgb => new byte[]
{
0, 3, 6, 9, 1, 4, 7, 10, 2, 5, 8, 11, 0xFF, 0xFF, 0xFF, 0xFF,
0, 3, 6, 9, 1, 4, 7, 10, 2, 5, 8, 11, 0xFF, 0xFF, 0xFF, 0xFF

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

@ -3,7 +3,6 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg
@ -17,56 +16,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
public bool IgnoreMetadata { get; set; }
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(stream, nameof(stream));
using var decoder = new JpegDecoderCore(configuration, this);
return decoder.Decode<TPixel>(configuration, stream);
return decoder.Decode<TPixel>(configuration, stream, cancellationToken);
}
/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream)
=> this.Decode<Rgb24>(configuration, stream);
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgb24>(configuration, stream, cancellationToken);
/// <inheritdoc/>
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(stream, nameof(stream));
using var decoder = new JpegDecoderCore(configuration, this);
return decoder.DecodeAsync<TPixel>(configuration, stream, cancellationToken);
}
/// <inheritdoc />
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> await this.DecodeAsync<Rgb24>(configuration, stream, cancellationToken)
.ConfigureAwait(false);
/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream)
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);
}
/// <inheritdoc/>
public async Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(stream, nameof(stream));
// The introduction of a local variable that refers to an object the implements
// IDisposable means you must use async/await, where the compiler generates the
// state machine and a continuation.
using (var decoder = new JpegDecoderCore(configuration, this))
{
return await decoder.IdentifyAsync(configuration, stream, cancellationToken)
.ConfigureAwait(false);
}
return decoder.Identify(configuration, stream, cancellationToken);
}
}
}

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

@ -677,7 +677,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
/// <summary>
/// Processes the App1 marker retrieving any stored metadata
/// Processes the App1 marker retrieving any stored metadata.
/// </summary>
/// <param name="stream">The input stream.</param>
/// <param name="remaining">The remaining bytes in the segment block.</param>
@ -687,7 +687,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
const int XmpMarkerLength = 29;
if (remaining < ExifMarkerLength || this.IgnoreMetadata)
{
// Skip the application header length
// Skip the application header length.
stream.Skip(remaining);
return;
}
@ -697,12 +697,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
JpegThrowHelper.ThrowInvalidImageContentException("Bad App1 Marker length.");
}
// XMP marker is the longest, so read at least that many bytes into temp.
// XMP marker is the longer then the EXIF marker, so first try read the EXIF marker bytes.
stream.Read(this.temp, 0, ExifMarkerLength);
remaining -= ExifMarkerLength;
if (ProfileResolver.IsProfile(this.temp, ProfileResolver.ExifMarker))
{
remaining -= ExifMarkerLength;
this.hasExif = true;
byte[] profile = new byte[remaining];
stream.Read(profile, 0, remaining);
@ -713,7 +713,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
else
{
// If the EXIF information exceeds 64K, it will be split over multiple APP1 markers
// If the EXIF information exceeds 64K, it will be split over multiple APP1 markers.
this.ExtendProfile(ref this.exifData, profile);
}
@ -722,9 +722,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
if (ProfileResolver.IsProfile(this.temp, ProfileResolver.XmpMarker.Slice(0, ExifMarkerLength)))
{
stream.Read(this.temp, 0, XmpMarkerLength - ExifMarkerLength);
remaining -= XmpMarkerLength;
if (ProfileResolver.IsProfile(this.temp, ProfileResolver.XmpMarker.Slice(ExifMarkerLength)))
int remainingXmpMarkerBytes = XmpMarkerLength - ExifMarkerLength;
stream.Read(this.temp, ExifMarkerLength, remainingXmpMarkerBytes);
remaining -= remainingXmpMarkerBytes;
if (ProfileResolver.IsProfile(this.temp, ProfileResolver.XmpMarker))
{
this.hasXmp = true;
byte[] profile = new byte[remaining];
@ -736,7 +737,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
else
{
// If the XMP information exceeds 64K, it will be split over multiple APP1 markers
// If the XMP information exceeds 64K, it will be split over multiple APP1 markers.
this.ExtendProfile(ref this.xmpData, profile);
}

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

@ -3,7 +3,6 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Pbm
@ -30,50 +29,26 @@ namespace SixLabors.ImageSharp.Formats.Pbm
public sealed class PbmDecoder : IImageDecoder, IImageInfoDetector
{
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(stream, nameof(stream));
var decoder = new PbmDecoderCore(configuration);
return decoder.Decode<TPixel>(configuration, stream);
return decoder.Decode<TPixel>(configuration, stream, cancellationToken);
}
/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream)
=> this.Decode<Rgb24>(configuration, stream);
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgb24>(configuration, stream, cancellationToken);
/// <inheritdoc/>
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(stream, nameof(stream));
var decoder = new PbmDecoderCore(configuration);
return decoder.DecodeAsync<TPixel>(configuration, stream, cancellationToken);
}
/// <inheritdoc />
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken)
.ConfigureAwait(false);
/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream)
{
Guard.NotNull(stream, nameof(stream));
var decoder = new PbmDecoderCore(configuration);
return decoder.Identify(configuration, stream);
}
/// <inheritdoc/>
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(stream, nameof(stream));
var decoder = new PbmDecoderCore(configuration);
return decoder.IdentifyAsync(configuration, stream, cancellationToken);
return decoder.Identify(configuration, stream, cancellationToken);
}
}
}

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

@ -3,7 +3,6 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Png
@ -17,18 +16,18 @@ namespace SixLabors.ImageSharp.Formats.Png
public bool IgnoreMetadata { get; set; }
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
PngDecoderCore decoder = new(configuration, this);
return decoder.Decode<TPixel>(configuration, stream);
return decoder.Decode<TPixel>(configuration, stream, cancellationToken);
}
/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream)
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
PngDecoderCore decoder = new(configuration, true);
IImageInfo info = decoder.Identify(configuration, stream);
IImageInfo info = decoder.Identify(configuration, stream, cancellationToken);
stream.Position = 0;
PngMetadata meta = info.Metadata.GetPngMetadata();
@ -40,118 +39,49 @@ namespace SixLabors.ImageSharp.Formats.Png
if (bits == PngBitDepth.Bit16)
{
return !meta.HasTransparency
? this.Decode<L16>(configuration, stream)
: this.Decode<La32>(configuration, stream);
? this.Decode<L16>(configuration, stream, cancellationToken)
: this.Decode<La32>(configuration, stream, cancellationToken);
}
return !meta.HasTransparency
? this.Decode<L8>(configuration, stream)
: this.Decode<La16>(configuration, stream);
? this.Decode<L8>(configuration, stream, cancellationToken)
: this.Decode<La16>(configuration, stream, cancellationToken);
case PngColorType.Rgb:
if (bits == PngBitDepth.Bit16)
{
return !meta.HasTransparency
? this.Decode<Rgb48>(configuration, stream)
: this.Decode<Rgba64>(configuration, stream);
? this.Decode<Rgb48>(configuration, stream, cancellationToken)
: this.Decode<Rgba64>(configuration, stream, cancellationToken);
}
return !meta.HasTransparency
? this.Decode<Rgb24>(configuration, stream)
: this.Decode<Rgba32>(configuration, stream);
? this.Decode<Rgb24>(configuration, stream, cancellationToken)
: this.Decode<Rgba32>(configuration, stream, cancellationToken);
case PngColorType.Palette:
return this.Decode<Rgba32>(configuration, stream);
return this.Decode<Rgba32>(configuration, stream, cancellationToken);
case PngColorType.GrayscaleWithAlpha:
return (bits == PngBitDepth.Bit16)
? this.Decode<La32>(configuration, stream)
: this.Decode<La16>(configuration, stream);
? this.Decode<La32>(configuration, stream, cancellationToken)
: this.Decode<La16>(configuration, stream, cancellationToken);
case PngColorType.RgbWithAlpha:
return (bits == PngBitDepth.Bit16)
? this.Decode<Rgba64>(configuration, stream)
: this.Decode<Rgba32>(configuration, stream);
? this.Decode<Rgba64>(configuration, stream, cancellationToken)
: this.Decode<Rgba32>(configuration, stream, cancellationToken);
default:
return this.Decode<Rgba32>(configuration, stream);
return this.Decode<Rgba32>(configuration, stream, cancellationToken);
}
}
/// <inheritdoc/>
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
PngDecoderCore decoder = new(configuration, this);
return decoder.DecodeAsync<TPixel>(configuration, stream, cancellationToken);
}
/// <inheritdoc />
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
PngDecoderCore decoder = new(configuration, true);
IImageInfo info = await decoder.IdentifyAsync(configuration, stream, cancellationToken).ConfigureAwait(false);
stream.Position = 0;
PngMetadata meta = info.Metadata.GetPngMetadata();
PngColorType color = meta.ColorType.GetValueOrDefault();
PngBitDepth bits = meta.BitDepth.GetValueOrDefault();
switch (color)
{
case PngColorType.Grayscale:
if (bits == PngBitDepth.Bit16)
{
return !meta.HasTransparency
? await this.DecodeAsync<L16>(configuration, stream, cancellationToken).ConfigureAwait(false)
: await this.DecodeAsync<La32>(configuration, stream, cancellationToken).ConfigureAwait(false);
}
return !meta.HasTransparency
? await this.DecodeAsync<L8>(configuration, stream, cancellationToken).ConfigureAwait(false)
: await this.DecodeAsync<La16>(configuration, stream, cancellationToken).ConfigureAwait(false);
case PngColorType.Rgb:
if (bits == PngBitDepth.Bit16)
{
return !meta.HasTransparency
? await this.DecodeAsync<Rgb48>(configuration, stream, cancellationToken).ConfigureAwait(false)
: await this.DecodeAsync<Rgba64>(configuration, stream, cancellationToken).ConfigureAwait(false);
}
return !meta.HasTransparency
? await this.DecodeAsync<Rgb24>(configuration, stream, cancellationToken).ConfigureAwait(false)
: await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken).ConfigureAwait(false);
case PngColorType.Palette:
return await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken).ConfigureAwait(false);
case PngColorType.GrayscaleWithAlpha:
return (bits == PngBitDepth.Bit16)
? await this.DecodeAsync<La32>(configuration, stream, cancellationToken).ConfigureAwait(false)
: await this.DecodeAsync<La16>(configuration, stream, cancellationToken).ConfigureAwait(false);
case PngColorType.RgbWithAlpha:
return (bits == PngBitDepth.Bit16)
? await this.DecodeAsync<Rgba64>(configuration, stream, cancellationToken).ConfigureAwait(false)
: await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken).ConfigureAwait(false);
default:
return await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken).ConfigureAwait(false);
}
}
/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream)
{
PngDecoderCore decoder = new(configuration, this);
return decoder.Identify(configuration, stream);
}
/// <inheritdoc/>
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
PngDecoderCore decoder = new(configuration, this);
return decoder.IdentifyAsync(configuration, stream, cancellationToken);
return decoder.Identify(configuration, stream, cancellationToken);
}
}
}

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

@ -3,9 +3,6 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tga
@ -16,48 +13,25 @@ namespace SixLabors.ImageSharp.Formats.Tga
public sealed class TgaDecoder : IImageDecoder, ITgaDecoderOptions, IImageInfoDetector
{
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(stream, nameof(stream));
var decoder = new TgaDecoderCore(configuration, this);
return decoder.Decode<TPixel>(configuration, stream);
return decoder.Decode<TPixel>(configuration, stream, cancellationToken);
}
/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream)
=> this.Decode<Rgba32>(configuration, stream);
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>(configuration, stream, cancellationToken);
/// <inheritdoc/>
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(stream, nameof(stream));
var decoder = new TgaDecoderCore(configuration, this);
return decoder.DecodeAsync<TPixel>(configuration, stream, cancellationToken);
}
/// <inheritdoc />
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken)
.ConfigureAwait(false);
/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream)
{
Guard.NotNull(stream, nameof(stream));
return new TgaDecoderCore(configuration, this).Identify(configuration, stream);
}
/// <inheritdoc/>
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(stream, nameof(stream));
return new TgaDecoderCore(configuration, this).IdentifyAsync(configuration, stream, cancellationToken);
return new TgaDecoderCore(configuration, this).Identify(configuration, stream, cancellationToken);
}
}
}

222
src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs

@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
/// <param name="pixelBytes">Buffer with decompressed pixel data.</param>
/// <param name="width">The width of the image or strip.</param>
/// <param name="colorType">The color type of the pixel data.</param>
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
/// <param name="isBigEndian">If set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public static void Undo(Span<byte> pixelBytes, int width, TiffColorType colorType, bool isBigEndian)
{
switch (colorType)
@ -43,12 +43,21 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
case TiffColorType.Rgb888:
UndoRgb24Bit(pixelBytes, width);
break;
case TiffColorType.Rgba8888:
UndoRgba32Bit(pixelBytes, width);
break;
case TiffColorType.Rgb161616:
UndoRgb48Bit(pixelBytes, width, isBigEndian);
break;
case TiffColorType.Rgba16161616:
UndoRgba64Bit(pixelBytes, width, isBigEndian);
break;
case TiffColorType.Rgb323232:
UndoRgb96Bit(pixelBytes, width, isBigEndian);
break;
case TiffColorType.Rgba32323232:
UndoRgba128Bit(pixelBytes, width, isBigEndian);
break;
}
}
@ -243,6 +252,33 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
}
}
private static void UndoRgba32Bit(Span<byte> pixelBytes, int width)
{
int rowBytesCount = width * 4;
int height = pixelBytes.Length / rowBytesCount;
for (int y = 0; y < height; y++)
{
Span<byte> rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount);
Span<Rgba32> rowRgb = MemoryMarshal.Cast<byte, Rgba32>(rowBytes).Slice(0, width);
ref Rgba32 rowRgbBase = ref MemoryMarshal.GetReference(rowRgb);
byte r = rowRgbBase.R;
byte g = rowRgbBase.G;
byte b = rowRgbBase.B;
byte a = rowRgbBase.A;
for (int x = 1; x < rowRgb.Length; x++)
{
ref Rgba32 pixel = ref rowRgb[x];
r += pixel.R;
g += pixel.G;
b += pixel.B;
a += pixel.A;
var rgb = new Rgba32(r, g, b, a);
pixel.FromRgba32(rgb);
}
}
}
private static void UndoRgb48Bit(Span<byte> pixelBytes, int width, bool isBigEndian)
{
int rowBytesCount = width * 6;
@ -319,6 +355,98 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
}
}
private static void UndoRgba64Bit(Span<byte> pixelBytes, int width, bool isBigEndian)
{
int rowBytesCount = width * 8;
int height = pixelBytes.Length / rowBytesCount;
if (isBigEndian)
{
for (int y = 0; y < height; y++)
{
int offset = 0;
Span<byte> rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount);
ushort r = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2));
offset += 2;
ushort g = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2));
offset += 2;
ushort b = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2));
offset += 2;
ushort a = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2));
offset += 2;
for (int x = 1; x < width; x++)
{
Span<byte> rowSpan = rowBytes.Slice(offset, 2);
ushort deltaR = TiffUtils.ConvertToUShortBigEndian(rowSpan);
r += deltaR;
BinaryPrimitives.WriteUInt16BigEndian(rowSpan, r);
offset += 2;
rowSpan = rowBytes.Slice(offset, 2);
ushort deltaG = TiffUtils.ConvertToUShortBigEndian(rowSpan);
g += deltaG;
BinaryPrimitives.WriteUInt16BigEndian(rowSpan, g);
offset += 2;
rowSpan = rowBytes.Slice(offset, 2);
ushort deltaB = TiffUtils.ConvertToUShortBigEndian(rowSpan);
b += deltaB;
BinaryPrimitives.WriteUInt16BigEndian(rowSpan, b);
offset += 2;
rowSpan = rowBytes.Slice(offset, 2);
ushort deltaA = TiffUtils.ConvertToUShortBigEndian(rowSpan);
a += deltaA;
BinaryPrimitives.WriteUInt16BigEndian(rowSpan, a);
offset += 2;
}
}
}
else
{
for (int y = 0; y < height; y++)
{
int offset = 0;
Span<byte> rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount);
ushort r = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2));
offset += 2;
ushort g = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2));
offset += 2;
ushort b = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2));
offset += 2;
ushort a = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2));
offset += 2;
for (int x = 1; x < width; x++)
{
Span<byte> rowSpan = rowBytes.Slice(offset, 2);
ushort deltaR = TiffUtils.ConvertToUShortLittleEndian(rowSpan);
r += deltaR;
BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, r);
offset += 2;
rowSpan = rowBytes.Slice(offset, 2);
ushort deltaG = TiffUtils.ConvertToUShortLittleEndian(rowSpan);
g += deltaG;
BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, g);
offset += 2;
rowSpan = rowBytes.Slice(offset, 2);
ushort deltaB = TiffUtils.ConvertToUShortLittleEndian(rowSpan);
b += deltaB;
BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, b);
offset += 2;
rowSpan = rowBytes.Slice(offset, 2);
ushort deltaA = TiffUtils.ConvertToUShortLittleEndian(rowSpan);
a += deltaA;
BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, a);
offset += 2;
}
}
}
}
private static void UndoRgb96Bit(Span<byte> pixelBytes, int width, bool isBigEndian)
{
int rowBytesCount = width * 12;
@ -394,5 +522,97 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
}
}
}
private static void UndoRgba128Bit(Span<byte> pixelBytes, int width, bool isBigEndian)
{
int rowBytesCount = width * 16;
int height = pixelBytes.Length / rowBytesCount;
if (isBigEndian)
{
for (int y = 0; y < height; y++)
{
int offset = 0;
Span<byte> rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount);
uint r = TiffUtils.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4));
offset += 4;
uint g = TiffUtils.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4));
offset += 4;
uint b = TiffUtils.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4));
offset += 4;
uint a = TiffUtils.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4));
offset += 4;
for (int x = 1; x < width; x++)
{
Span<byte> rowSpan = rowBytes.Slice(offset, 4);
uint deltaR = TiffUtils.ConvertToUIntBigEndian(rowSpan);
r += deltaR;
BinaryPrimitives.WriteUInt32BigEndian(rowSpan, r);
offset += 4;
rowSpan = rowBytes.Slice(offset, 4);
uint deltaG = TiffUtils.ConvertToUIntBigEndian(rowSpan);
g += deltaG;
BinaryPrimitives.WriteUInt32BigEndian(rowSpan, g);
offset += 4;
rowSpan = rowBytes.Slice(offset, 4);
uint deltaB = TiffUtils.ConvertToUIntBigEndian(rowSpan);
b += deltaB;
BinaryPrimitives.WriteUInt32BigEndian(rowSpan, b);
offset += 4;
rowSpan = rowBytes.Slice(offset, 4);
uint deltaA = TiffUtils.ConvertToUIntBigEndian(rowSpan);
a += deltaA;
BinaryPrimitives.WriteUInt32BigEndian(rowSpan, a);
offset += 4;
}
}
}
else
{
for (int y = 0; y < height; y++)
{
int offset = 0;
Span<byte> rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount);
uint r = TiffUtils.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4));
offset += 4;
uint g = TiffUtils.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4));
offset += 4;
uint b = TiffUtils.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4));
offset += 4;
uint a = TiffUtils.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4));
offset += 4;
for (int x = 1; x < width; x++)
{
Span<byte> rowSpan = rowBytes.Slice(offset, 4);
uint deltaR = TiffUtils.ConvertToUIntLittleEndian(rowSpan);
r += deltaR;
BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, r);
offset += 4;
rowSpan = rowBytes.Slice(offset, 4);
uint deltaG = TiffUtils.ConvertToUIntLittleEndian(rowSpan);
g += deltaG;
BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, g);
offset += 4;
rowSpan = rowBytes.Slice(offset, 4);
uint deltaB = TiffUtils.ConvertToUIntLittleEndian(rowSpan);
b += deltaB;
BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, b);
offset += 4;
rowSpan = rowBytes.Slice(offset, 4);
uint deltaA = TiffUtils.ConvertToUIntLittleEndian(rowSpan);
a += deltaA;
BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, a);
offset += 4;
}
}
}
}
}
}

4
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs

@ -30,10 +30,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
int offset = 0;
byte[] buffer = new byte[4];
Span<byte> buffer = stackalloc byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0;
Span<byte> bufferSpan = buffer.AsSpan(bufferStartIdx);
Span<byte> bufferSpan = buffer.Slice(bufferStartIdx);
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);

4
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs

@ -30,13 +30,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
byte[] buffer = new byte[4];
Span<byte> buffer = stackalloc byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0;
Span<byte> redData = data[0].GetSpan();
Span<byte> greenData = data[1].GetSpan();
Span<byte> blueData = data[2].GetSpan();
Span<byte> bufferSpan = buffer.AsSpan(bufferStartIdx);
Span<byte> bufferSpan = buffer.Slice(bufferStartIdx);
int offset = 0;
for (int y = top; y < top + height; y++)

77
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16161616TiffColor{TPixel}.cs

@ -0,0 +1,77 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
/// <summary>
/// Implements the 'RGB' photometric interpretation with an alpha channel and with 16 bits for each channel.
/// </summary>
internal class Rgba16161616TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly bool isBigEndian;
private readonly Configuration configuration;
/// <summary>
/// Initializes a new instance of the <see cref="Rgba16161616TiffColor{TPixel}" /> class.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public Rgba16161616TiffColor(Configuration configuration, bool isBigEndian)
{
this.configuration = configuration;
this.isBigEndian = isBigEndian;
}
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
Rgba64 rgba = TiffUtils.Rgba64Default;
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
int offset = 0;
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
if (this.isBigEndian)
{
for (int x = 0; x < pixelRow.Length; x++)
{
ulong r = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2));
offset += 2;
ulong g = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2));
offset += 2;
ulong b = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2));
offset += 2;
ulong a = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2));
offset += 2;
pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, a, color);
}
}
else
{
int byteCount = pixelRow.Length * 8;
PixelOperations<TPixel>.Instance.FromRgba64Bytes(
this.configuration,
data.Slice(offset, byteCount),
pixelRow,
pixelRow.Length);
offset += byteCount;
}
}
}
}
}

75
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16PlanarTiffColor{TPixel}.cs

@ -0,0 +1,75 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
/// <summary>
/// Implements the 'RGB' photometric interpretation with an alpha channel and with 'Planar' layout for each color channel with 16 bit.
/// </summary>
internal class Rgba16PlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly bool isBigEndian;
/// <summary>
/// Initializes a new instance of the <see cref="Rgba16PlanarTiffColor{TPixel}" /> class.
/// </summary>
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public Rgba16PlanarTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian;
/// <inheritdoc/>
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
Rgba64 rgba = TiffUtils.Rgba64Default;
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
Span<byte> redData = data[0].GetSpan();
Span<byte> greenData = data[1].GetSpan();
Span<byte> blueData = data[2].GetSpan();
Span<byte> alphaData = data[3].GetSpan();
int offset = 0;
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
if (this.isBigEndian)
{
for (int x = 0; x < pixelRow.Length; x++)
{
ulong r = TiffUtils.ConvertToUShortBigEndian(redData.Slice(offset, 2));
ulong g = TiffUtils.ConvertToUShortBigEndian(greenData.Slice(offset, 2));
ulong b = TiffUtils.ConvertToUShortBigEndian(blueData.Slice(offset, 2));
ulong a = TiffUtils.ConvertToUShortBigEndian(alphaData.Slice(offset, 2));
offset += 2;
pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, a, color);
}
}
else
{
for (int x = 0; x < pixelRow.Length; x++)
{
ulong r = TiffUtils.ConvertToUShortLittleEndian(redData.Slice(offset, 2));
ulong g = TiffUtils.ConvertToUShortLittleEndian(greenData.Slice(offset, 2));
ulong b = TiffUtils.ConvertToUShortLittleEndian(blueData.Slice(offset, 2));
ulong a = TiffUtils.ConvertToUShortBigEndian(alphaData.Slice(offset, 2));
offset += 2;
pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, a, color);
}
}
}
}
}
}

90
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24242424TiffColor{TPixel}.cs

@ -0,0 +1,90 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
/// <summary>
/// Implements the 'RGB' photometric interpretation with an alpha channel and with 24 bits for each channel.
/// </summary>
internal class Rgba24242424TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly bool isBigEndian;
/// <summary>
/// Initializes a new instance of the <see cref="Rgba24242424TiffColor{TPixel}" /> class.
/// </summary>
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public Rgba24242424TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian;
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
int offset = 0;
Span<byte> buffer = stackalloc byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0;
Span<byte> bufferSpan = buffer.Slice(bufferStartIdx);
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
if (this.isBigEndian)
{
for (int x = 0; x < pixelRow.Length; x++)
{
data.Slice(offset, 3).CopyTo(bufferSpan);
ulong r = TiffUtils.ConvertToUIntBigEndian(buffer);
offset += 3;
data.Slice(offset, 3).CopyTo(bufferSpan);
ulong g = TiffUtils.ConvertToUIntBigEndian(buffer);
offset += 3;
data.Slice(offset, 3).CopyTo(bufferSpan);
ulong b = TiffUtils.ConvertToUIntBigEndian(buffer);
offset += 3;
data.Slice(offset, 3).CopyTo(bufferSpan);
ulong a = TiffUtils.ConvertToUIntBigEndian(buffer);
offset += 3;
pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, a, color);
}
}
else
{
for (int x = 0; x < pixelRow.Length; x++)
{
data.Slice(offset, 3).CopyTo(bufferSpan);
ulong r = TiffUtils.ConvertToUIntLittleEndian(buffer);
offset += 3;
data.Slice(offset, 3).CopyTo(bufferSpan);
ulong g = TiffUtils.ConvertToUIntLittleEndian(buffer);
offset += 3;
data.Slice(offset, 3).CopyTo(bufferSpan);
ulong b = TiffUtils.ConvertToUIntLittleEndian(buffer);
offset += 3;
data.Slice(offset, 3).CopyTo(bufferSpan);
ulong a = TiffUtils.ConvertToUIntLittleEndian(buffer);
offset += 3;
pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, a, color);
}
}
}
}
}
}

85
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24PlanarTiffColor{TPixel}.cs

@ -0,0 +1,85 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
/// <summary>
/// Implements the 'RGB' photometric interpretation with an alpha channel and with 'Planar' layout for each color channel with 24 bit.
/// </summary>
internal class Rgba24PlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly bool isBigEndian;
/// <summary>
/// Initializes a new instance of the <see cref="Rgba24PlanarTiffColor{TPixel}" /> class.
/// </summary>
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public Rgba24PlanarTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian;
/// <inheritdoc/>
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
Span<byte> buffer = stackalloc byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0;
Span<byte> redData = data[0].GetSpan();
Span<byte> greenData = data[1].GetSpan();
Span<byte> blueData = data[2].GetSpan();
Span<byte> alphaData = data[3].GetSpan();
Span<byte> bufferSpan = buffer.Slice(bufferStartIdx);
int offset = 0;
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
if (this.isBigEndian)
{
for (int x = 0; x < pixelRow.Length; x++)
{
redData.Slice(offset, 3).CopyTo(bufferSpan);
ulong r = TiffUtils.ConvertToUIntBigEndian(buffer);
greenData.Slice(offset, 3).CopyTo(bufferSpan);
ulong g = TiffUtils.ConvertToUIntBigEndian(buffer);
blueData.Slice(offset, 3).CopyTo(bufferSpan);
ulong b = TiffUtils.ConvertToUIntBigEndian(buffer);
alphaData.Slice(offset, 3).CopyTo(bufferSpan);
ulong a = TiffUtils.ConvertToUIntBigEndian(buffer);
offset += 3;
pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, a, color);
}
}
else
{
for (int x = 0; x < pixelRow.Length; x++)
{
redData.Slice(offset, 3).CopyTo(bufferSpan);
ulong r = TiffUtils.ConvertToUIntLittleEndian(buffer);
greenData.Slice(offset, 3).CopyTo(bufferSpan);
ulong g = TiffUtils.ConvertToUIntLittleEndian(buffer);
blueData.Slice(offset, 3).CopyTo(bufferSpan);
ulong b = TiffUtils.ConvertToUIntLittleEndian(buffer);
alphaData.Slice(offset, 3).CopyTo(bufferSpan);
ulong a = TiffUtils.ConvertToUIntLittleEndian(buffer);
offset += 3;
pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, a, color);
}
}
}
}
}
}

79
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32323232TiffColor{TPixel}.cs

@ -0,0 +1,79 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
/// <summary>
/// Implements the 'RGB' photometric interpretation with an alpha channel and with 32 bits for each channel.
/// </summary>
internal class Rgba32323232TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly bool isBigEndian;
/// <summary>
/// Initializes a new instance of the <see cref="Rgba32323232TiffColor{TPixel}" /> class.
/// </summary>
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public Rgba32323232TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian;
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
int offset = 0;
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
if (this.isBigEndian)
{
for (int x = 0; x < pixelRow.Length; x++)
{
ulong r = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4));
offset += 4;
ulong g = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4));
offset += 4;
ulong b = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4));
offset += 4;
ulong a = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4));
offset += 4;
pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, a, color);
}
}
else
{
for (int x = 0; x < pixelRow.Length; x++)
{
ulong r = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4));
offset += 4;
ulong g = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4));
offset += 4;
ulong b = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4));
offset += 4;
ulong a = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4));
offset += 4;
pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, a, color);
}
}
}
}
}
}

74
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32PlanarTiffColor{TPixel}.cs

@ -0,0 +1,74 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
/// <summary>
/// Implements the 'RGB' photometric interpretation with an alpha channel and a 'Planar' layout for each color channel with 32 bit.
/// </summary>
internal class Rgba32PlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly bool isBigEndian;
/// <summary>
/// Initializes a new instance of the <see cref="Rgba32PlanarTiffColor{TPixel}" /> class.
/// </summary>
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public Rgba32PlanarTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian;
/// <inheritdoc/>
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
Span<byte> redData = data[0].GetSpan();
Span<byte> greenData = data[1].GetSpan();
Span<byte> blueData = data[2].GetSpan();
Span<byte> alphaData = data[3].GetSpan();
int offset = 0;
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
if (this.isBigEndian)
{
for (int x = 0; x < pixelRow.Length; x++)
{
ulong r = TiffUtils.ConvertToUIntBigEndian(redData.Slice(offset, 4));
ulong g = TiffUtils.ConvertToUIntBigEndian(greenData.Slice(offset, 4));
ulong b = TiffUtils.ConvertToUIntBigEndian(blueData.Slice(offset, 4));
ulong a = TiffUtils.ConvertToUIntBigEndian(alphaData.Slice(offset, 4));
offset += 4;
pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, a, color);
}
}
else
{
for (int x = 0; x < pixelRow.Length; x++)
{
ulong r = TiffUtils.ConvertToUIntLittleEndian(redData.Slice(offset, 4));
ulong g = TiffUtils.ConvertToUIntLittleEndian(greenData.Slice(offset, 4));
ulong b = TiffUtils.ConvertToUIntLittleEndian(blueData.Slice(offset, 4));
ulong a = TiffUtils.ConvertToUIntLittleEndian(alphaData.Slice(offset, 4));
offset += 4;
pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, a, color);
}
}
}
}
}
}

39
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba8888TiffColor{TPixel}.cs

@ -0,0 +1,39 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
/// <summary>
/// Implements the 'RGB' photometric interpretation with an alpha channel and 8 bits per channel.
/// </summary>
internal class Rgba8888TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly Configuration configuration;
public Rgba8888TiffColor(Configuration configuration) => this.configuration = configuration;
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
int offset = 0;
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
int byteCount = pixelRow.Length * 4;
PixelOperations<TPixel>.Instance.FromRgba32Bytes(
this.configuration,
data.Slice(offset, byteCount),
pixelRow,
pixelRow.Length);
offset += byteCount;
}
}
}
}

97
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaFloat32323232TiffColor{TPixel}.cs

@ -0,0 +1,97 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
/// <summary>
/// Implements the 'RGB' photometric interpretation with an alpha channel and with 32 bits for each channel.
/// </summary>
internal class RgbaFloat32323232TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly bool isBigEndian;
/// <summary>
/// Initializes a new instance of the <see cref="RgbaFloat32323232TiffColor{TPixel}" /> class.
/// </summary>
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public RgbaFloat32323232TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian;
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
int offset = 0;
byte[] buffer = new byte[4];
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
if (this.isBigEndian)
{
for (int x = 0; x < pixelRow.Length; x++)
{
data.Slice(offset, 4).CopyTo(buffer);
Array.Reverse(buffer);
float r = BitConverter.ToSingle(buffer, 0);
offset += 4;
data.Slice(offset, 4).CopyTo(buffer);
Array.Reverse(buffer);
float g = BitConverter.ToSingle(buffer, 0);
offset += 4;
data.Slice(offset, 4).CopyTo(buffer);
Array.Reverse(buffer);
float b = BitConverter.ToSingle(buffer, 0);
offset += 4;
data.Slice(offset, 4).CopyTo(buffer);
Array.Reverse(buffer);
float a = BitConverter.ToSingle(buffer, 0);
offset += 4;
var colorVector = new Vector4(r, g, b, a);
color.FromVector4(colorVector);
pixelRow[x] = color;
}
}
else
{
for (int x = 0; x < pixelRow.Length; x++)
{
data.Slice(offset, 4).CopyTo(buffer);
float r = BitConverter.ToSingle(buffer, 0);
offset += 4;
data.Slice(offset, 4).CopyTo(buffer);
float g = BitConverter.ToSingle(buffer, 0);
offset += 4;
data.Slice(offset, 4).CopyTo(buffer);
float b = BitConverter.ToSingle(buffer, 0);
offset += 4;
data.Slice(offset, 4).CopyTo(buffer);
float a = BitConverter.ToSingle(buffer, 0);
offset += 4;
var colorVector = new Vector4(r, g, b, a);
color.FromVector4(colorVector);
pixelRow[x] = color;
}
}
}
}
}
}

87
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaPlanarTiffColor{TPixel}.cs

@ -0,0 +1,87 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
/// <summary>
/// Implements the 'RGB' photometric interpretation with an alpha channel and with 'Planar' layout (for all bit depths).
/// </summary>
internal class RgbaPlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly float rFactor;
private readonly float gFactor;
private readonly float bFactor;
private readonly float aFactor;
private readonly ushort bitsPerSampleR;
private readonly ushort bitsPerSampleG;
private readonly ushort bitsPerSampleB;
private readonly ushort bitsPerSampleA;
public RgbaPlanarTiffColor(TiffBitsPerSample bitsPerSample)
{
this.bitsPerSampleR = bitsPerSample.Channel0;
this.bitsPerSampleG = bitsPerSample.Channel1;
this.bitsPerSampleB = bitsPerSample.Channel2;
this.bitsPerSampleA = bitsPerSample.Channel3;
this.rFactor = (1 << this.bitsPerSampleR) - 1.0f;
this.gFactor = (1 << this.bitsPerSampleG) - 1.0f;
this.bFactor = (1 << this.bitsPerSampleB) - 1.0f;
this.aFactor = (1 << this.bitsPerSampleA) - 1.0f;
}
/// <summary>
/// Decodes pixel data using the current photometric interpretation.
/// </summary>
/// <param name="data">The buffers to read image data from.</param>
/// <param name="pixels">The image buffer to write pixels to.</param>
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
/// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param>
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
var rBitReader = new BitReader(data[0].GetSpan());
var gBitReader = new BitReader(data[1].GetSpan());
var bBitReader = new BitReader(data[2].GetSpan());
var aBitReader = new BitReader(data[3].GetSpan());
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
for (int x = 0; x < pixelRow.Length; x++)
{
float r = rBitReader.ReadBits(this.bitsPerSampleR) / this.rFactor;
float g = gBitReader.ReadBits(this.bitsPerSampleG) / this.gFactor;
float b = bBitReader.ReadBits(this.bitsPerSampleB) / this.bFactor;
float a = aBitReader.ReadBits(this.bitsPerSampleA) / this.aFactor;
color.FromVector4(new Vector4(r, g, b, a));
pixelRow[x] = color;
}
rBitReader.NextRow();
gBitReader.NextRow();
bBitReader.NextRow();
aBitReader.NextRow();
}
}
}
}

72
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaTiffColor{TPixel}.cs

@ -0,0 +1,72 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
/// <summary>
/// Implements the 'RGB' photometric interpretation with alpha channel (for all bit depths).
/// </summary>
internal class RgbaTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly float rFactor;
private readonly float gFactor;
private readonly float bFactor;
private readonly float aFactor;
private readonly ushort bitsPerSampleR;
private readonly ushort bitsPerSampleG;
private readonly ushort bitsPerSampleB;
private readonly ushort bitsPerSampleA;
public RgbaTiffColor(TiffBitsPerSample bitsPerSample)
{
this.bitsPerSampleR = bitsPerSample.Channel0;
this.bitsPerSampleG = bitsPerSample.Channel1;
this.bitsPerSampleB = bitsPerSample.Channel2;
this.bitsPerSampleA = bitsPerSample.Channel3;
this.rFactor = (1 << this.bitsPerSampleR) - 1.0f;
this.gFactor = (1 << this.bitsPerSampleG) - 1.0f;
this.bFactor = (1 << this.bitsPerSampleB) - 1.0f;
this.aFactor = (1 << this.bitsPerSampleA) - 1.0f;
}
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
var bitReader = new BitReader(data);
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
for (int x = 0; x < pixelRow.Length; x++)
{
float r = bitReader.ReadBits(this.bitsPerSampleR) / this.rFactor;
float g = bitReader.ReadBits(this.bitsPerSampleG) / this.gFactor;
float b = bitReader.ReadBits(this.bitsPerSampleB) / this.bFactor;
float a = bitReader.ReadBits(this.bitsPerSampleB) / this.aFactor;
color.FromVector4(new Vector4(r, g, b, a));
pixelRow[x] = color;
}
bitReader.NextRow();
}
}
}
}

189
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs

@ -116,6 +116,38 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbTiffColor<TPixel>(bitsPerSample);
case TiffColorType.Rgba2222:
DebugGuard.IsTrue(
bitsPerSample.Channels == 4
&& bitsPerSample.Channel3 == 2
&& bitsPerSample.Channel2 == 2
&& bitsPerSample.Channel1 == 2
&& bitsPerSample.Channel0 == 2,
"bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbaTiffColor<TPixel>(bitsPerSample);
case TiffColorType.Rgb333:
DebugGuard.IsTrue(
bitsPerSample.Channels == 3
&& bitsPerSample.Channel2 == 3
&& bitsPerSample.Channel1 == 3
&& bitsPerSample.Channel0 == 3,
"bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbTiffColor<TPixel>(bitsPerSample);
case TiffColorType.Rgba3333:
DebugGuard.IsTrue(
bitsPerSample.Channels == 4
&& bitsPerSample.Channel3 == 3
&& bitsPerSample.Channel2 == 3
&& bitsPerSample.Channel1 == 3
&& bitsPerSample.Channel0 == 3,
"bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbaTiffColor<TPixel>(bitsPerSample);
case TiffColorType.Rgb444:
DebugGuard.IsTrue(
bitsPerSample.Channels == 3
@ -126,6 +158,59 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgb444TiffColor<TPixel>();
case TiffColorType.Rgba4444:
DebugGuard.IsTrue(
bitsPerSample.Channels == 4
&& bitsPerSample.Channel3 == 4
&& bitsPerSample.Channel2 == 4
&& bitsPerSample.Channel1 == 4
&& bitsPerSample.Channel0 == 4,
"bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbaTiffColor<TPixel>(bitsPerSample);
case TiffColorType.Rgb555:
DebugGuard.IsTrue(
bitsPerSample.Channels == 3
&& bitsPerSample.Channel2 == 5
&& bitsPerSample.Channel1 == 5
&& bitsPerSample.Channel0 == 5,
"bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbTiffColor<TPixel>(bitsPerSample);
case TiffColorType.Rgba5555:
DebugGuard.IsTrue(
bitsPerSample.Channels == 4
&& bitsPerSample.Channel3 == 5
&& bitsPerSample.Channel2 == 5
&& bitsPerSample.Channel1 == 5
&& bitsPerSample.Channel0 == 5,
"bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbaTiffColor<TPixel>(bitsPerSample);
case TiffColorType.Rgb666:
DebugGuard.IsTrue(
bitsPerSample.Channels == 3
&& bitsPerSample.Channel2 == 6
&& bitsPerSample.Channel1 == 6
&& bitsPerSample.Channel0 == 6,
"bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbTiffColor<TPixel>(bitsPerSample);
case TiffColorType.Rgba6666:
DebugGuard.IsTrue(
bitsPerSample.Channels == 4
&& bitsPerSample.Channel3 == 6
&& bitsPerSample.Channel2 == 6
&& bitsPerSample.Channel1 == 6
&& bitsPerSample.Channel0 == 6,
"bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbaTiffColor<TPixel>(bitsPerSample);
case TiffColorType.Rgb888:
DebugGuard.IsTrue(
bitsPerSample.Channels == 3
@ -136,6 +221,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgb888TiffColor<TPixel>(configuration);
case TiffColorType.Rgba8888:
DebugGuard.IsTrue(
bitsPerSample.Channels == 4
&& bitsPerSample.Channel3 == 8
&& bitsPerSample.Channel2 == 8
&& bitsPerSample.Channel1 == 8
&& bitsPerSample.Channel0 == 8,
"bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgba8888TiffColor<TPixel>(configuration);
case TiffColorType.Rgb101010:
DebugGuard.IsTrue(
bitsPerSample.Channels == 3
@ -146,6 +242,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbTiffColor<TPixel>(bitsPerSample);
case TiffColorType.Rgba10101010:
DebugGuard.IsTrue(
bitsPerSample.Channels == 4
&& bitsPerSample.Channel3 == 10
&& bitsPerSample.Channel2 == 10
&& bitsPerSample.Channel1 == 10
&& bitsPerSample.Channel0 == 10,
"bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbaTiffColor<TPixel>(bitsPerSample);
case TiffColorType.Rgb121212:
DebugGuard.IsTrue(
bitsPerSample.Channels == 3
@ -156,6 +263,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbTiffColor<TPixel>(bitsPerSample);
case TiffColorType.Rgba12121212:
DebugGuard.IsTrue(
bitsPerSample.Channels == 4
&& bitsPerSample.Channel3 == 12
&& bitsPerSample.Channel2 == 12
&& bitsPerSample.Channel1 == 12
&& bitsPerSample.Channel0 == 12,
"bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbaTiffColor<TPixel>(bitsPerSample);
case TiffColorType.Rgb141414:
DebugGuard.IsTrue(
bitsPerSample.Channels == 3
@ -166,6 +284,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbTiffColor<TPixel>(bitsPerSample);
case TiffColorType.Rgba14141414:
DebugGuard.IsTrue(
bitsPerSample.Channels == 4
&& bitsPerSample.Channel3 == 14
&& bitsPerSample.Channel2 == 14
&& bitsPerSample.Channel1 == 14
&& bitsPerSample.Channel0 == 14,
"bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbaTiffColor<TPixel>(bitsPerSample);
case TiffColorType.Rgb161616:
DebugGuard.IsTrue(
bitsPerSample.Channels == 3
@ -176,6 +305,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgb161616TiffColor<TPixel>(configuration, isBigEndian: byteOrder == ByteOrder.BigEndian);
case TiffColorType.Rgba16161616:
DebugGuard.IsTrue(
bitsPerSample.Channels == 4
&& bitsPerSample.Channel3 == 16
&& bitsPerSample.Channel2 == 16
&& bitsPerSample.Channel1 == 16
&& bitsPerSample.Channel0 == 16,
"bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgba16161616TiffColor<TPixel>(configuration, isBigEndian: byteOrder == ByteOrder.BigEndian);
case TiffColorType.Rgb242424:
DebugGuard.IsTrue(
bitsPerSample.Channels == 3
@ -186,6 +326,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgb242424TiffColor<TPixel>(isBigEndian: byteOrder == ByteOrder.BigEndian);
case TiffColorType.Rgba24242424:
DebugGuard.IsTrue(
bitsPerSample.Channels == 4
&& bitsPerSample.Channel3 == 24
&& bitsPerSample.Channel2 == 24
&& bitsPerSample.Channel1 == 24
&& bitsPerSample.Channel0 == 24,
"bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgba24242424TiffColor<TPixel>(isBigEndian: byteOrder == ByteOrder.BigEndian);
case TiffColorType.Rgb323232:
DebugGuard.IsTrue(
bitsPerSample.Channels == 3
@ -196,6 +347,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgb323232TiffColor<TPixel>(isBigEndian: byteOrder == ByteOrder.BigEndian);
case TiffColorType.Rgba32323232:
DebugGuard.IsTrue(
bitsPerSample.Channels == 4
&& bitsPerSample.Channel3 == 32
&& bitsPerSample.Channel2 == 32
&& bitsPerSample.Channel1 == 32
&& bitsPerSample.Channel0 == 32,
"bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgba32323232TiffColor<TPixel>(isBigEndian: byteOrder == ByteOrder.BigEndian);
case TiffColorType.RgbFloat323232:
DebugGuard.IsTrue(
bitsPerSample.Channels == 3
@ -206,6 +368,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbFloat323232TiffColor<TPixel>(isBigEndian: byteOrder == ByteOrder.BigEndian);
case TiffColorType.RgbaFloat32323232:
DebugGuard.IsTrue(
bitsPerSample.Channels == 4
&& bitsPerSample.Channel3 == 32
&& bitsPerSample.Channel2 == 32
&& bitsPerSample.Channel1 == 32
&& bitsPerSample.Channel0 == 32,
"bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbaFloat32323232TiffColor<TPixel>(isBigEndian: byteOrder == ByteOrder.BigEndian);
case TiffColorType.PaletteColor:
DebugGuard.NotNull(colorMap, "colorMap");
return new PaletteTiffColor<TPixel>(bitsPerSample, colorMap);
@ -233,6 +406,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbPlanarTiffColor<TPixel>(bitsPerSample);
case TiffColorType.Rgba8888Planar:
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbaPlanarTiffColor<TPixel>(bitsPerSample);
case TiffColorType.YCbCrPlanar:
return new YCbCrPlanarTiffColor<TPixel>(referenceBlackAndWhite, ycbcrCoefficients, ycbcrSubSampling);
@ -240,14 +417,26 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgb16PlanarTiffColor<TPixel>(byteOrder == ByteOrder.BigEndian);
case TiffColorType.Rgba16161616Planar:
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgba16PlanarTiffColor<TPixel>(byteOrder == ByteOrder.BigEndian);
case TiffColorType.Rgb242424Planar:
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgb24PlanarTiffColor<TPixel>(byteOrder == ByteOrder.BigEndian);
case TiffColorType.Rgba24242424Planar:
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgba24PlanarTiffColor<TPixel>(byteOrder == ByteOrder.BigEndian);
case TiffColorType.Rgb323232Planar:
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgb32PlanarTiffColor<TPixel>(byteOrder == ByteOrder.BigEndian);
case TiffColorType.Rgba32323232Planar:
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgba32PlanarTiffColor<TPixel>(byteOrder == ByteOrder.BigEndian);
default:
throw TiffThrowHelper.InvalidColorType(colorType.ToString());
}

100
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs

@ -103,71 +103,171 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// </summary>
Rgb222,
/// <summary>
/// RGBA color image with 2 bits for each channel.
/// </summary>
Rgba2222,
/// <summary>
/// RGB color image with 3 bits for each channel.
/// </summary>
Rgb333,
/// <summary>
/// RGBA color image with 3 bits for each channel.
/// </summary>
Rgba3333,
/// <summary>
/// RGB color image with 4 bits for each channel.
/// </summary>
Rgb444,
/// <summary>
/// RGBA color image with 4 bits for each channel.
/// </summary>
Rgba4444,
/// <summary>
/// RGB color image with 5 bits for each channel.
/// </summary>
Rgb555,
/// <summary>
/// RGBA color image with 5 bits for each channel.
/// </summary>
Rgba5555,
/// <summary>
/// RGB color image with 6 bits for each channel.
/// </summary>
Rgb666,
/// <summary>
/// RGBA color image with 6 bits for each channel.
/// </summary>
Rgba6666,
/// <summary>
/// RGB Full Color. Optimized implementation for 8-bit images.
/// </summary>
Rgb888,
/// <summary>
/// RGBA Full Color with 8-bit for each channel.
/// </summary>
Rgba8888,
/// <summary>
/// RGB color image with 10 bits for each channel.
/// </summary>
Rgb101010,
/// <summary>
/// RGBA color image with 10 bits for each channel.
/// </summary>
Rgba10101010,
/// <summary>
/// RGB color image with 12 bits for each channel.
/// </summary>
Rgb121212,
/// <summary>
/// RGBA color image with 12 bits for each channel.
/// </summary>
Rgba12121212,
/// <summary>
/// RGB color image with 14 bits for each channel.
/// </summary>
Rgb141414,
/// <summary>
/// RGBA color image with 14 bits for each channel.
/// </summary>
Rgba14141414,
/// <summary>
/// RGB color image with 16 bits for each channel.
/// </summary>
Rgb161616,
/// <summary>
/// RGBA color image with 16 bits for each channel.
/// </summary>
Rgba16161616,
/// <summary>
/// RGB color image with 24 bits for each channel.
/// </summary>
Rgb242424,
/// <summary>
/// RGBA color image with 24 bits for each channel.
/// </summary>
Rgba24242424,
/// <summary>
/// RGB color image with 32 bits for each channel.
/// </summary>
Rgb323232,
/// <summary>
/// RGBA color image with 32 bits for each channel.
/// </summary>
Rgba32323232,
/// <summary>
/// RGB color image with 32 bits floats for each channel.
/// </summary>
RgbFloat323232,
/// <summary>
/// RGBA color image with 32 bits floats for each channel.
/// </summary>
RgbaFloat32323232,
/// <summary>
/// RGB Full Color. Planar configuration of data. 8 Bit per color channel.
/// </summary>
Rgb888Planar,
/// <summary>
/// RGBA color image with an alpha channel. Planar configuration of data. 8 Bit per color channel.
/// </summary>
Rgba8888Planar,
/// <summary>
/// RGB Full Color. Planar configuration of data. 16 Bit per color channel.
/// </summary>
Rgb161616Planar,
/// <summary>
/// RGB Color with an alpha channel. Planar configuration of data. 16 Bit per color channel.
/// </summary>
Rgba16161616Planar,
/// <summary>
/// RGB Full Color. Planar configuration of data. 24 Bit per color channel.
/// </summary>
Rgb242424Planar,
/// <summary>
/// RGB Color with an alpha channel. Planar configuration of data. 24 Bit per color channel.
/// </summary>
Rgba24242424Planar,
/// <summary>
/// RGB Full Color. Planar configuration of data. 32 Bit per color channel.
/// </summary>
Rgb323232Planar,
/// <summary>
/// RGB Color with an alpha channel. Planar configuration of data. 32 Bit per color channel.
/// </summary>
Rgba32323232Planar,
/// <summary>
/// The pixels are stored in YCbCr format.
/// </summary>

2
src/ImageSharp/Formats/Tiff/README.md

@ -105,7 +105,7 @@
|Artist | Y | Y | |
|HostComputer | Y | Y | |
|ColorMap | Y | Y | |
|ExtraSamples | | - | |
|ExtraSamples | | (Y) | Only UnassociatedAlphaData is supported so far |
|Copyright | Y | Y | |
### Extension TIFF Tags

38
src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs

@ -25,6 +25,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// </summary>
public readonly ushort Channel2;
/// <summary>
/// The bits for the alpha channel.
/// </summary>
public readonly ushort Channel3;
/// <summary>
/// The number of channels.
/// </summary>
@ -36,16 +41,19 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <param name="channel0">The bits for the channel 0.</param>
/// <param name="channel1">The bits for the channel 1.</param>
/// <param name="channel2">The bits for the channel 2.</param>
public TiffBitsPerSample(ushort channel0, ushort channel1, ushort channel2)
/// <param name="channel3">The bits for the channel 3.</param>
public TiffBitsPerSample(ushort channel0, ushort channel1, ushort channel2, ushort channel3 = 0)
{
this.Channel0 = (ushort)Numerics.Clamp(channel0, 0, 32);
this.Channel1 = (ushort)Numerics.Clamp(channel1, 0, 32);
this.Channel2 = (ushort)Numerics.Clamp(channel2, 0, 32);
this.Channel3 = (ushort)Numerics.Clamp(channel3, 0, 32);
this.Channels = 0;
this.Channels += (byte)(this.Channel0 != 0 ? 1 : 0);
this.Channels += (byte)(this.Channel1 != 0 ? 1 : 0);
this.Channels += (byte)(this.Channel2 != 0 ? 1 : 0);
this.Channels += (byte)(this.Channel3 != 0 ? 1 : 0);
}
/// <summary>
@ -62,11 +70,19 @@ namespace SixLabors.ImageSharp.Formats.Tiff
return false;
}
ushort c3 = 0;
ushort c2;
ushort c1;
ushort c0;
switch (value.Length)
{
case 4:
c3 = value[3];
c2 = value[2];
c1 = value[1];
c0 = value[0];
break;
case 3:
c2 = value[2];
c1 = value[1];
@ -84,7 +100,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
break;
}
sample = new TiffBitsPerSample(c0, c1, c2);
sample = new TiffBitsPerSample(c0, c1, c2, c3);
return true;
}
@ -96,11 +112,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff
public bool Equals(TiffBitsPerSample other)
=> this.Channel0 == other.Channel0
&& this.Channel1 == other.Channel1
&& this.Channel2 == other.Channel2;
&& this.Channel2 == other.Channel2
&& this.Channel3 == other.Channel3;
/// <inheritdoc/>
public override int GetHashCode()
=> HashCode.Combine(this.Channel0, this.Channel1, this.Channel2);
=> HashCode.Combine(this.Channel0, this.Channel1, this.Channel2, this.Channel3);
/// <summary>
/// Converts the bits per sample struct to an ushort array.
@ -118,7 +135,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff
return new[] { this.Channel0, this.Channel1 };
}
return new[] { this.Channel0, this.Channel1, this.Channel2 };
if (this.Channel3 == 0)
{
return new[] { this.Channel0, this.Channel1, this.Channel2 };
}
return new[] { this.Channel0, this.Channel1, this.Channel2, this.Channel3 };
}
/// <summary>
@ -127,12 +149,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <returns>Bits per pixel.</returns>
public TiffBitsPerPixel BitsPerPixel()
{
int bitsPerPixel = this.Channel0 + this.Channel1 + this.Channel2;
int bitsPerPixel = this.Channel0 + this.Channel1 + this.Channel2 + this.Channel3;
return (TiffBitsPerPixel)bitsPerPixel;
}
/// <inheritdoc/>
public override string ToString()
=> $"TiffBitsPerSample({this.Channel0}, {this.Channel1}, {this.Channel2})";
=> this.Channel3 is 0 ?
$"TiffBitsPerSample({this.Channel0}, {this.Channel1}, {this.Channel2})"
: $"TiffBitsPerSample({this.Channel0}, {this.Channel1}, {this.Channel2}, {this.Channel3})";
}
}

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

@ -3,7 +3,6 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
@ -25,49 +24,26 @@ namespace SixLabors.ImageSharp.Formats.Tiff
public FrameDecodingMode DecodingMode { get; set; }
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(stream, "stream");
var decoder = new TiffDecoderCore(configuration, this);
return decoder.Decode<TPixel>(configuration, stream);
}
/// <inheritdoc/>
public Image Decode(Configuration configuration, Stream stream) => this.Decode<Rgba32>(configuration, stream);
/// <inheritdoc/>
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(stream, nameof(stream));
var decoder = new TiffDecoderCore(configuration, this);
return decoder.DecodeAsync<TPixel>(configuration, stream, cancellationToken);
return decoder.Decode<TPixel>(configuration, stream, cancellationToken);
}
/// <inheritdoc/>
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken)
.ConfigureAwait(false);
/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream)
{
Guard.NotNull(stream, nameof(stream));
var decoder = new TiffDecoderCore(configuration, this);
return decoder.Identify(configuration, stream);
}
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>(configuration, stream, cancellationToken);
/// <inheritdoc/>
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(stream, nameof(stream));
var decoder = new TiffDecoderCore(configuration, this);
return decoder.IdentifyAsync(configuration, stream, cancellationToken);
return decoder.Identify(configuration, stream, cancellationToken);
}
}
}

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

@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <summary>
/// Gets the decoding mode for multi-frame images
/// </summary>
private FrameDecodingMode decodingMode;
private readonly FrameDecodingMode decodingMode;
/// <summary>
/// The stream to decode from.
@ -117,6 +117,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// </summary>
public TiffFillOrder FillOrder { get; set; }
/// <summary>
/// Gets or sets the extra samples, which can contain the alpha channel data.
/// </summary>
public TiffExtraSampleType? ExtraSamples { get; set; }
/// <summary>
/// Gets or sets the JPEG tables when jpeg compression is used.
/// </summary>
@ -275,12 +280,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff
return memory;
}
else
{
DebugGuard.IsTrue(array is ulong[], $"Expected {nameof(UInt64)} array.");
span = (ulong[])array;
return null;
}
DebugGuard.IsTrue(array is ulong[], $"Expected {nameof(UInt64)} array.");
span = (ulong[])array;
return null;
}
/// <summary>
@ -314,8 +317,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff
case 2:
bitsPerPixel = this.BitsPerSample.Channel2;
break;
case 3:
bitsPerPixel = this.BitsPerSample.Channel2;
break;
default:
TiffThrowHelper.ThrowNotSupported("More then 3 color channels are not supported");
TiffThrowHelper.ThrowNotSupported("More then 4 color channels are not supported");
break;
}
}

60
src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs

@ -29,9 +29,21 @@ namespace SixLabors.ImageSharp.Formats.Tiff
TiffThrowHelper.ThrowNotSupported("Tiled images are not supported.");
}
if (exifProfile.GetValueInternal(ExifTag.ExtraSamples) is not null)
IExifValue extraSamplesExifValue = exifProfile.GetValueInternal(ExifTag.ExtraSamples);
if (extraSamplesExifValue is not null)
{
TiffThrowHelper.ThrowNotSupported("ExtraSamples is not supported.");
short[] extraSamples = (short[])extraSamplesExifValue.GetValue();
if (extraSamples.Length != 1)
{
TiffThrowHelper.ThrowNotSupported("ExtraSamples is only supported with one extra sample for alpha data.");
}
var extraSamplesType = (TiffExtraSampleType)extraSamples[0];
options.ExtraSamples = extraSamplesType;
if (extraSamplesType is not TiffExtraSampleType.UnassociatedAlphaData)
{
TiffThrowHelper.ThrowNotSupported("Decoding Tiff images with ExtraSamples is only supported with UnassociatedAlphaData.");
}
}
TiffFillOrder fillOrder = (TiffFillOrder?)exifProfile.GetValue(ExifTag.FillOrder)?.Value ?? TiffFillOrder.MostSignificantBitFirst;
@ -52,7 +64,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
sampleFormat = sampleFormats[0];
foreach (TiffSampleFormat format in sampleFormats)
{
if (format != TiffSampleFormat.UnsignedInteger && format != TiffSampleFormat.Float)
if (format is not TiffSampleFormat.UnsignedInteger and not TiffSampleFormat.Float)
{
TiffThrowHelper.ThrowNotSupported("ImageSharp only supports the UnsignedInteger and Float SampleFormat.");
}
@ -252,12 +264,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff
case TiffPhotometricInterpretation.Rgb:
{
TiffBitsPerSample bitsPerSample = options.BitsPerSample;
if (bitsPerSample.Channels != 3)
if (bitsPerSample.Channels is not (3 or 4))
{
TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported.");
}
if (!(bitsPerSample.Channel0 == bitsPerSample.Channel1 && bitsPerSample.Channel1 == bitsPerSample.Channel2))
if ((bitsPerSample.Channels == 3 && !(bitsPerSample.Channel0 == bitsPerSample.Channel1 && bitsPerSample.Channel1 == bitsPerSample.Channel2)) ||
(bitsPerSample.Channels == 4 && !(bitsPerSample.Channel0 == bitsPerSample.Channel1 && bitsPerSample.Channel1 == bitsPerSample.Channel2 && bitsPerSample.Channel2 == bitsPerSample.Channel3)))
{
TiffThrowHelper.ThrowNotSupported("Only BitsPerSample with equal bits per channel are supported.");
}
@ -270,41 +283,50 @@ namespace SixLabors.ImageSharp.Formats.Tiff
case 32:
if (options.SampleFormat == TiffSampleFormat.Float)
{
options.ColorType = TiffColorType.RgbFloat323232;
options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.RgbFloat323232 : TiffColorType.RgbaFloat32323232;
return;
}
options.ColorType = TiffColorType.Rgb323232;
options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb323232 : TiffColorType.Rgba32323232;
break;
case 24:
options.ColorType = TiffColorType.Rgb242424;
options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb242424 : TiffColorType.Rgba24242424;
break;
case 16:
options.ColorType = TiffColorType.Rgb161616;
options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb161616 : TiffColorType.Rgba16161616;
break;
case 14:
options.ColorType = TiffColorType.Rgb141414;
options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb141414 : TiffColorType.Rgba14141414;
break;
case 12:
options.ColorType = TiffColorType.Rgb121212;
options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb121212 : TiffColorType.Rgba12121212;
break;
case 10:
options.ColorType = TiffColorType.Rgb101010;
options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb101010 : TiffColorType.Rgba10101010;
break;
case 8:
options.ColorType = TiffColorType.Rgb888;
options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb888 : TiffColorType.Rgba8888;
break;
case 6:
options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb666 : TiffColorType.Rgba6666;
break;
case 5:
options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb555 : TiffColorType.Rgba5555;
break;
case 4:
options.ColorType = TiffColorType.Rgb444;
options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb444 : TiffColorType.Rgba4444;
break;
case 3:
options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb333 : TiffColorType.Rgba3333;
break;
case 2:
options.ColorType = TiffColorType.Rgb222;
options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb222 : TiffColorType.Rgba2222;
break;
default:
TiffThrowHelper.ThrowNotSupported("Bits per sample is not supported.");
@ -317,16 +339,16 @@ namespace SixLabors.ImageSharp.Formats.Tiff
switch (bitsPerChannel)
{
case 32:
options.ColorType = TiffColorType.Rgb323232Planar;
options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb323232Planar : TiffColorType.Rgba32323232Planar;
break;
case 24:
options.ColorType = TiffColorType.Rgb242424Planar;
options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb242424Planar : TiffColorType.Rgba24242424Planar;
break;
case 16:
options.ColorType = TiffColorType.Rgb161616Planar;
options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb161616Planar : TiffColorType.Rgba16161616Planar;
break;
default:
options.ColorType = TiffColorType.Rgb888Planar;
options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb888Planar : TiffColorType.Rgba8888Planar;
break;
}
}

27
src/ImageSharp/Formats/Tiff/TiffExtraSampleType.cs

@ -0,0 +1,27 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Tiff
{
/// <summary>
/// Description of extra components.
/// </summary>
internal enum TiffExtraSampleType
{
/// <summary>
/// The data is unspecified, not supported.
/// </summary>
UnspecifiedData = 0,
/// <summary>
/// The extra data is associated alpha data (with pre-multiplied color).
/// </summary>
AssociatedAlphaData = 1,
/// <summary>
/// The extra data is unassociated alpha data is transparency information that logically exists independent of an image;
/// it is commonly called a soft matte.
/// </summary>
UnassociatedAlphaData = 2
}
}

33
src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs

@ -18,11 +18,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils
private const float Scale32Bit = 1.0f / 0xFFFFFFFF;
public static Vector4 Vector4Default { get; } = new Vector4(0.0f, 0.0f, 0.0f, 0.0f);
public static Vector4 Vector4Default { get; } = new(0.0f, 0.0f, 0.0f, 0.0f);
public static Rgba64 Rgba64Default { get; } = new Rgba64(0, 0, 0, 0);
public static Rgba64 Rgba64Default { get; } = new(0, 0, 0, 0);
public static L16 L16Default { get; } = new L16(0);
public static L16 L16Default { get; } = new(0);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ushort ConvertToUShortBigEndian(ReadOnlySpan<byte> buffer) => BinaryPrimitives.ReadUInt16BigEndian(buffer);
@ -54,6 +54,15 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils
return color;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorFromRgba64<TPixel>(Rgba64 rgba, ulong r, ulong g, ulong b, ulong a, TPixel color)
where TPixel : unmanaged, IPixel<TPixel>
{
rgba.PackedValue = r | (g << 16) | (b << 32) | (a << 48);
color.FromRgba64(rgba);
return color;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorScaleTo24Bit<TPixel>(ulong r, ulong g, ulong b, TPixel color)
where TPixel : unmanaged, IPixel<TPixel>
@ -63,6 +72,15 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils
return color;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorScaleTo24Bit<TPixel>(ulong r, ulong g, ulong b, ulong a, TPixel color)
where TPixel : unmanaged, IPixel<TPixel>
{
var colorVector = new Vector4(r * Scale24Bit, g * Scale24Bit, b * Scale24Bit, a * Scale24Bit);
color.FromVector4(colorVector);
return color;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorScaleTo32Bit<TPixel>(ulong r, ulong g, ulong b, TPixel color)
where TPixel : unmanaged, IPixel<TPixel>
@ -72,6 +90,15 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils
return color;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorScaleTo32Bit<TPixel>(ulong r, ulong g, ulong b, ulong a, TPixel color)
where TPixel : unmanaged, IPixel<TPixel>
{
var colorVector = new Vector4(r * Scale32Bit, g * Scale32Bit, b * Scale32Bit, a * Scale32Bit);
color.FromVector4(colorVector);
return color;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorFromL16<TPixel>(L16 l16, ushort intensity, TPixel color)
where TPixel : unmanaged, IPixel<TPixel>

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

@ -3,8 +3,6 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
@ -28,7 +26,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
public FrameDecodingMode DecodingMode { get; set; } = FrameDecodingMode.All;
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(stream, nameof(stream));
@ -37,39 +35,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
try
{
return decoder.Decode<TPixel>(configuration, stream);
}
catch (InvalidMemoryOperationException ex)
{
Size dims = decoder.Dimensions;
throw new InvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex);
}
}
/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream)
{
Guard.NotNull(stream, nameof(stream));
return new WebpDecoderCore(configuration, this).Identify(configuration, stream);
}
/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream) => this.Decode<Rgba32>(configuration, stream);
/// <inheritdoc />
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(stream, nameof(stream));
using var decoder = new WebpDecoderCore(configuration, this);
try
{
using var bufferedStream = new BufferedReadStream(configuration, stream);
return decoder.DecodeAsync<TPixel>(configuration, bufferedStream, cancellationToken);
return decoder.Decode<TPixel>(configuration, stream, cancellationToken);
}
catch (InvalidMemoryOperationException ex)
{
@ -80,16 +46,15 @@ namespace SixLabors.ImageSharp.Formats.Webp
}
/// <inheritdoc />
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken).ConfigureAwait(false);
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>(configuration, stream, cancellationToken);
/// <inheritdoc />
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(stream, nameof(stream));
using var bufferedStream = new BufferedReadStream(configuration, stream);
return new WebpDecoderCore(configuration, this).IdentifyAsync(configuration, bufferedStream, cancellationToken);
return new WebpDecoderCore(configuration, this).Identify(configuration, stream, cancellationToken);
}
}
}

56
src/ImageSharp/IO/ChunkedMemoryStream.cs

@ -16,19 +16,17 @@ namespace SixLabors.ImageSharp.IO
/// </summary>
internal sealed class ChunkedMemoryStream : Stream
{
/// <summary>
/// The default length in bytes of each buffer chunk.
/// </summary>
public const int DefaultBufferLength = 128 * 1024;
// The memory allocator.
private readonly MemoryAllocator allocator;
// Data
private MemoryChunk memoryChunk;
// The length of each buffer chunk
private readonly int chunkLength;
// The total number of allocated chunks
private int chunkCount;
// The length of the largest contiguous buffer that can be handled by the allocator.
private readonly int allocatorCapacity;
// Has the stream been disposed.
private bool isDisposed;
@ -49,21 +47,10 @@ namespace SixLabors.ImageSharp.IO
/// Initializes a new instance of the <see cref="ChunkedMemoryStream"/> class.
/// </summary>
public ChunkedMemoryStream(MemoryAllocator allocator)
: this(DefaultBufferLength, allocator)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ChunkedMemoryStream"/> class.
/// </summary>
/// <param name="bufferLength">The length, in bytes of each buffer chunk.</param>
/// <param name="allocator">The memory allocator.</param>
public ChunkedMemoryStream(int bufferLength, MemoryAllocator allocator)
{
Guard.MustBeGreaterThan(bufferLength, 0, nameof(bufferLength));
Guard.NotNull(allocator, nameof(allocator));
this.chunkLength = bufferLength;
this.allocatorCapacity = allocator.GetBufferCapacityInBytes();
this.allocator = allocator;
}
@ -191,6 +178,9 @@ namespace SixLabors.ImageSharp.IO
case SeekOrigin.End:
this.Position = this.Length + offset;
break;
default:
ThrowInvalidSeek();
break;
}
return this.Position;
@ -219,6 +209,7 @@ namespace SixLabors.ImageSharp.IO
this.memoryChunk = null;
this.writeChunk = null;
this.readChunk = null;
this.chunkCount = 0;
}
finally
{
@ -519,17 +510,22 @@ namespace SixLabors.ImageSharp.IO
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowDisposed()
=> throw new ObjectDisposedException(null, "The stream is closed.");
private static void ThrowDisposed() => throw new ObjectDisposedException(null, "The stream is closed.");
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowArgumentOutOfRange(string value)
=> throw new ArgumentOutOfRangeException(value);
private static void ThrowArgumentOutOfRange(string value) => throw new ArgumentOutOfRangeException(value);
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowInvalidSeek() => throw new ArgumentException("Invalid seek origin.");
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private MemoryChunk AllocateMemoryChunk()
{
IMemoryOwner<byte> buffer = this.allocator.Allocate<byte>(this.chunkLength);
// Tweak our buffer sizes to take the minimum of the provided buffer sizes
// or the allocator buffer capacity which provides us with the largest
// available contiguous buffer size.
IMemoryOwner<byte> buffer = this.allocator.Allocate<byte>(Math.Min(this.allocatorCapacity, GetChunkSize(this.chunkCount++)));
return new MemoryChunk
{
Buffer = buffer,
@ -547,6 +543,18 @@ namespace SixLabors.ImageSharp.IO
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetChunkSize(int i)
{
// Increment chunks sizes with moderate speed, but without using too many buffers from the same ArrayPool bucket of the default MemoryAllocator.
// https://github.com/SixLabors/ImageSharp/pull/2006#issuecomment-1066244720
#pragma warning disable IDE1006 // Naming Styles
const int _128K = 1 << 17;
const int _4M = 1 << 22;
return i < 16 ? _128K * (1 << (i / 4)) : _4M;
#pragma warning restore IDE1006 // Naming Styles
}
private sealed class MemoryChunk : IDisposable
{
private bool isDisposed;

118
src/ImageSharp/Image.Decode.cs

@ -2,11 +2,8 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
@ -25,7 +22,7 @@ namespace SixLabors.ImageSharp
/// The image might be filled with memory garbage.
/// </summary>
/// <typeparam name="TPixel">The pixel type</typeparam>
/// <param name="configuration">The <see cref="ImageSharp.Configuration"/></param>
/// <param name="configuration">The <see cref="Configuration"/></param>
/// <param name="width">The width of the image</param>
/// <param name="height">The height of the image</param>
/// <param name="metadata">The <see cref="ImageMetadata"/></param>
@ -98,20 +95,6 @@ namespace SixLabors.ImageSharp
return format;
}
/// <summary>
/// By reading the header on the provided stream this calculates the images format.
/// </summary>
/// <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 Task<IImageFormat> InternalDetectFormatAsync(Stream stream, Configuration config)
{
// We are going to cheat here because we know that by this point we have been wrapped in a
// seekable stream then we are free to use sync APIs this is potentially brittle and may
// need a better fix in the future.
return Task.FromResult(InternalDetectFormat(stream, config));
}
/// <summary>
/// By reading the header on the provided stream this calculates the images format.
/// </summary>
@ -128,33 +111,17 @@ namespace SixLabors.ImageSharp
: null;
}
/// <summary>
/// By reading the header on the provided stream this calculates the images format.
/// </summary>
/// <param name="stream">The image stream to read the header from.</param>
/// <param name="config">The configuration.</param>
/// <returns>The decoder and the image format or null if none found.</returns>
private static async Task<(IImageDecoder Decoder, IImageFormat Format)> DiscoverDecoderAsync(Stream stream, Configuration config)
{
IImageFormat format = await InternalDetectFormatAsync(stream, config).ConfigureAwait(false);
IImageDecoder decoder = format != null
? config.ImageFormatsManager.FindDecoder(format)
: null;
return (decoder, format);
}
/// <summary>
/// Decodes the image stream to the current image.
/// </summary>
/// <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)
private static (Image<TPixel> Image, IImageFormat Format) Decode<TPixel>(Stream stream, Configuration config, CancellationToken cancellationToken = default)
where TPixel : unmanaged, IPixel<TPixel>
{
IImageDecoder decoder = DiscoverDecoder(stream, config, out IImageFormat format);
@ -163,37 +130,11 @@ namespace SixLabors.ImageSharp
return (null, null);
}
Image<TPixel> img = decoder.Decode<TPixel>(config, stream);
return (img, format);
}
/// <summary>
/// Decodes the image stream to the current image.
/// </summary>
/// <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 <see cref="Task{ValueTuple}"/> representing the asynchronous operation.</returns>
private static async Task<(Image<TPixel> Image, IImageFormat Format)> DecodeAsync<TPixel>(
Stream stream,
Configuration config,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
(IImageDecoder decoder, IImageFormat format) = await DiscoverDecoderAsync(stream, config)
.ConfigureAwait(false);
if (decoder is null)
{
return (null, null);
}
Image<TPixel> img = await decoder.DecodeAsync<TPixel>(config, stream, cancellationToken)
.ConfigureAwait(false);
Image<TPixel> img = decoder.Decode<TPixel>(config, stream, cancellationToken);
return (img, format);
}
private static (Image Image, IImageFormat Format) Decode(Stream stream, Configuration config)
private static (Image Image, IImageFormat Format) Decode(Stream stream, Configuration config, CancellationToken cancellationToken = default)
{
IImageDecoder decoder = DiscoverDecoder(stream, config, out IImageFormat format);
if (decoder is null)
@ -201,19 +142,7 @@ namespace SixLabors.ImageSharp
return (null, null);
}
Image img = decoder.Decode(config, stream);
return (img, format);
}
private static async Task<(Image Image, IImageFormat Format)> DecodeAsync(Stream stream, Configuration config, CancellationToken cancellationToken)
{
(IImageDecoder decoder, IImageFormat format) = await DiscoverDecoderAsync(stream, config).ConfigureAwait(false);
if (decoder is null)
{
return (null, null);
}
Image img = await decoder.DecodeAsync(config, stream, cancellationToken).ConfigureAwait(false);
Image img = decoder.Decode(config, stream, cancellationToken);
return (img, format);
}
@ -222,47 +151,20 @@ namespace SixLabors.ImageSharp
/// </summary>
/// <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)
private static (IImageInfo ImageInfo, IImageFormat Format) InternalIdentity(Stream stream, Configuration config, CancellationToken cancellationToken = default)
{
IImageDecoder decoder = DiscoverDecoder(stream, config, out IImageFormat format);
if (!(decoder is IImageInfoDetector detector))
{
return (null, null);
}
IImageInfo info = detector?.Identify(config, stream);
return (info, format);
}
/// <summary>
/// Reads the raw image information from the specified stream.
/// </summary>
/// <param name="stream">The stream.</param>
/// <param name="config">the configuration.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>
/// A <see cref="Task{ValueTuple}"/> representing the asynchronous operation with the
/// <see cref="IImageInfo"/> property of the returned type set to null if a suitable detector
/// is not found.</returns>
private static async Task<(IImageInfo ImageInfo, IImageFormat Format)> InternalIdentityAsync(Stream stream, Configuration config, CancellationToken cancellationToken)
{
(IImageDecoder decoder, IImageFormat format) = await DiscoverDecoderAsync(stream, config).ConfigureAwait(false);
if (!(decoder is IImageInfoDetector detector))
if (decoder is not IImageInfoDetector detector)
{
return (null, null);
}
if (detector is null)
{
return (null, format);
}
IImageInfo info = await detector.IdentifyAsync(config, stream, cancellationToken).ConfigureAwait(false);
IImageInfo info = detector?.Identify(config, stream, cancellationToken);
return (info, format);
}
}

10
src/ImageSharp/Image.FromFile.cs

@ -298,7 +298,7 @@ namespace SixLabors.ImageSharp
/// <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(
public static async Task<Image> LoadAsync(
Configuration configuration,
string path,
IImageDecoder decoder,
@ -308,7 +308,8 @@ namespace SixLabors.ImageSharp
Guard.NotNull(path, nameof(path));
using Stream stream = configuration.FileSystem.OpenRead(path);
return LoadAsync(configuration, stream, decoder, cancellationToken);
return await LoadAsync(configuration, stream, decoder, cancellationToken)
.ConfigureAwait(false);
}
/// <summary>
@ -326,7 +327,7 @@ namespace SixLabors.ImageSharp
/// <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>(
public static async Task<Image<TPixel>> LoadAsync<TPixel>(
Configuration configuration,
string path,
IImageDecoder decoder,
@ -337,7 +338,8 @@ namespace SixLabors.ImageSharp
Guard.NotNull(path, nameof(path));
using Stream stream = configuration.FileSystem.OpenRead(path);
return LoadAsync<TPixel>(configuration, stream, decoder, cancellationToken);
return await LoadAsync<TPixel>(configuration, stream, decoder, cancellationToken)
.ConfigureAwait(false);
}
/// <summary>

36
src/ImageSharp/Image.FromStream.cs

@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp
=> WithSeekableStreamAsync(
configuration,
stream,
(s, _) => InternalDetectFormatAsync(s, configuration),
(s, _) => InternalDetectFormat(s, configuration),
cancellationToken);
/// <summary>
@ -208,7 +208,7 @@ namespace SixLabors.ImageSharp
=> WithSeekableStreamAsync(
configuration,
stream,
(s, ct) => InternalIdentityAsync(s, configuration ?? Configuration.Default, ct),
(s, ct) => InternalIdentity(s, configuration ?? Configuration.Default, ct),
cancellationToken);
/// <summary>
@ -313,7 +313,7 @@ namespace SixLabors.ImageSharp
public static Image Load(Configuration configuration, Stream stream, IImageDecoder decoder)
{
Guard.NotNull(decoder, nameof(decoder));
return WithSeekableStream(configuration, stream, s => decoder.Decode(configuration, s));
return WithSeekableStream(configuration, stream, s => decoder.Decode(configuration, s, default));
}
/// <summary>
@ -341,7 +341,7 @@ namespace SixLabors.ImageSharp
return WithSeekableStreamAsync(
configuration,
stream,
(s, ct) => decoder.DecodeAsync(configuration, s, ct),
(s, ct) => decoder.Decode(configuration, s, ct),
cancellationToken);
}
@ -432,9 +432,9 @@ namespace SixLabors.ImageSharp
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <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>(Stream stream, CancellationToken cancellationToken = default)
public static Task<(Image<TPixel> Image, IImageFormat Format)> LoadWithFormatAsync<TPixel>(Stream stream, CancellationToken cancellationToken = default)
where TPixel : unmanaged, IPixel<TPixel>
=> await LoadWithFormatAsync<TPixel>(Configuration.Default, stream, cancellationToken).ConfigureAwait(false);
=> LoadWithFormatAsync<TPixel>(Configuration.Default, stream, cancellationToken);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
@ -449,7 +449,7 @@ namespace SixLabors.ImageSharp
/// <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));
=> 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.
@ -468,7 +468,7 @@ namespace SixLabors.ImageSharp
=> WithSeekableStreamAsync(
Configuration.Default,
stream,
(s, ct) => decoder.DecodeAsync<TPixel>(Configuration.Default, s, ct),
(s, ct) => decoder.Decode<TPixel>(Configuration.Default, s, ct),
cancellationToken);
/// <summary>
@ -486,7 +486,7 @@ namespace SixLabors.ImageSharp
/// <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));
=> 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.
@ -511,7 +511,7 @@ namespace SixLabors.ImageSharp
=> WithSeekableStreamAsync(
configuration,
stream,
(s, ct) => decoder.DecodeAsync<TPixel>(configuration, s, ct),
(s, ct) => decoder.Decode<TPixel>(configuration, s, ct),
cancellationToken);
/// <summary>
@ -586,7 +586,7 @@ namespace SixLabors.ImageSharp
(Image Image, IImageFormat Format) data = await WithSeekableStreamAsync(
configuration,
stream,
async (s, ct) => await DecodeAsync(s, configuration, ct).ConfigureAwait(false),
(s, ct) => Decode(s, configuration, ct),
cancellationToken)
.ConfigureAwait(false);
@ -629,7 +629,7 @@ namespace SixLabors.ImageSharp
await WithSeekableStreamAsync(
configuration,
stream,
(s, ct) => DecodeAsync<TPixel>(s, configuration, ct),
(s, ct) => Decode<TPixel>(s, configuration, ct),
cancellationToken)
.ConfigureAwait(false);
@ -759,7 +759,7 @@ namespace SixLabors.ImageSharp
private static async Task<T> WithSeekableStreamAsync<T>(
Configuration configuration,
Stream stream,
Func<Stream, CancellationToken, Task<T>> action,
Func<Stream, CancellationToken, T> action,
CancellationToken cancellationToken)
{
Guard.NotNull(configuration, nameof(configuration));
@ -770,10 +770,6 @@ namespace SixLabors.ImageSharp
throw new NotSupportedException("Cannot read from the stream.");
}
// To make sure we don't trigger anything with aspnetcore then we just need to make sure we are
// seekable and we make the copy using CopyToAsync if the stream is seekable then we aren't using
// one of the aspnetcore wrapped streams that error on sync api calls and we can use it without
// having to further wrap
if (stream.CanSeek)
{
if (configuration.ReadOrigin == ReadOrigin.Begin)
@ -781,14 +777,16 @@ namespace SixLabors.ImageSharp
stream.Position = 0;
}
return await action(stream, cancellationToken).ConfigureAwait(false);
// NOTE: We are explicitly not executing the action against the stream here as we do in WithSeekableStream() because that
// would incur synchronous IO reads which must be avoided in this asynchronous method. Instead, we will *always* run the
// code below to copy the stream to an in-memory buffer before invoking the action.
}
using var memoryStream = new ChunkedMemoryStream(configuration.MemoryAllocator);
await stream.CopyToAsync(memoryStream, configuration.StreamProcessingBufferSize, cancellationToken).ConfigureAwait(false);
memoryStream.Position = 0;
return await action(memoryStream, cancellationToken).ConfigureAwait(false);
return action(memoryStream, cancellationToken);
}
}
}

6
src/ImageSharp/ImageFrame{TPixel}.cs

@ -362,10 +362,8 @@ namespace SixLabors.ImageSharp
return;
}
this.PixelBuffer.FastMemoryGroup.TransformTo(destination, (s, d) =>
{
PixelOperations<TPixel>.Instance.To(this.GetConfiguration(), s, d);
});
this.PixelBuffer.FastMemoryGroup.TransformTo(destination, (s, d)
=> PixelOperations<TPixel>.Instance.To(this.GetConfiguration(), s, d));
}
/// <inheritdoc/>

2
src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.String.cs

@ -94,7 +94,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
/// <summary>
/// Gets the MDFileUnits exif tag.
/// </summary>
public static ExifTag<string> MDFileUnits => new ExifTag<string>(ExifTagValue.MDFileUnits);
public static ExifTag<string> MDFileUnits { get; } = new ExifTag<string>(ExifTagValue.MDFileUnits);
/// <summary>
/// Gets the SEMInfo exif tag.

2
src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValue.cs

@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
{
// All array types are value types so Clone() is sufficient here.
var array = (Array)other.GetValue();
this.TrySetValue(array.Clone());
this.TrySetValue(array?.Clone());
}
}

4
src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/RgbaVector.PixelOperations.cs

@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.PixelFormats
internal class PixelOperations : PixelOperations<RgbaVector>
{
private static readonly Lazy<PixelTypeInfo> LazyInfo =
new Lazy<PixelTypeInfo>(() => PixelTypeInfo.Create<RgbaVector>(PixelAlphaRepresentation.Unassociated), true);
new(() => PixelTypeInfo.Create<RgbaVector>(PixelAlphaRepresentation.Unassociated), true);
/// <inheritdoc />
public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value;
@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.PixelFormats
{
Span<Vector4> destinationVectors = MemoryMarshal.Cast<RgbaVector, Vector4>(destinationPixels);
PixelOperations<TSourcePixel>.Instance.ToVector4(configuration, sourcePixels, destinationVectors);
PixelOperations<TSourcePixel>.Instance.ToVector4(configuration, sourcePixels, destinationVectors, PixelConversionModifiers.Scale);
}
/// <inheritdoc />

24
src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs

@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.PixelFormats
public partial class PixelOperations<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private static readonly Lazy<PixelTypeInfo> LazyInfo = new Lazy<PixelTypeInfo>(() => PixelTypeInfo.Create<TPixel>(), true);
private static readonly Lazy<PixelTypeInfo> LazyInfo = new(() => PixelTypeInfo.Create<TPixel>(), true);
/// <summary>
/// Gets the global <see cref="PixelOperations{TPixel}"/> instance for the pixel type <typeparamref name="TPixel"/>
@ -116,29 +116,29 @@ namespace SixLabors.ImageSharp.PixelFormats
Span<TPixel> destinationPixels)
where TSourcePixel : unmanaged, IPixel<TSourcePixel>
{
const int SliceLength = 1024;
int numberOfSlices = sourcePixels.Length / SliceLength;
const int sliceLength = 1024;
int numberOfSlices = sourcePixels.Length / sliceLength;
using IMemoryOwner<Vector4> tempVectors = configuration.MemoryAllocator.Allocate<Vector4>(SliceLength);
using IMemoryOwner<Vector4> tempVectors = configuration.MemoryAllocator.Allocate<Vector4>(sliceLength);
Span<Vector4> vectorSpan = tempVectors.GetSpan();
for (int i = 0; i < numberOfSlices; i++)
{
int start = i * SliceLength;
ReadOnlySpan<TSourcePixel> s = sourcePixels.Slice(start, SliceLength);
Span<TPixel> d = destinationPixels.Slice(start, SliceLength);
PixelOperations<TSourcePixel>.Instance.ToVector4(configuration, s, vectorSpan);
this.FromVector4Destructive(configuration, vectorSpan, d);
int start = i * sliceLength;
ReadOnlySpan<TSourcePixel> s = sourcePixels.Slice(start, sliceLength);
Span<TPixel> d = destinationPixels.Slice(start, sliceLength);
PixelOperations<TSourcePixel>.Instance.ToVector4(configuration, s, vectorSpan, PixelConversionModifiers.Scale);
this.FromVector4Destructive(configuration, vectorSpan, d, PixelConversionModifiers.Scale);
}
int endOfCompleteSlices = numberOfSlices * SliceLength;
int endOfCompleteSlices = numberOfSlices * sliceLength;
int remainder = sourcePixels.Length - endOfCompleteSlices;
if (remainder > 0)
{
ReadOnlySpan<TSourcePixel> s = sourcePixels.Slice(endOfCompleteSlices);
Span<TPixel> d = destinationPixels.Slice(endOfCompleteSlices);
vectorSpan = vectorSpan.Slice(0, remainder);
PixelOperations<TSourcePixel>.Instance.ToVector4(configuration, s, vectorSpan);
this.FromVector4Destructive(configuration, vectorSpan, d);
PixelOperations<TSourcePixel>.Instance.ToVector4(configuration, s, vectorSpan, PixelConversionModifiers.Scale);
this.FromVector4Destructive(configuration, vectorSpan, d, PixelConversionModifiers.Scale);
}
}

6
src/ImageSharp/Processing/Extensions/Transforms/PadExtensions.cs

@ -1,6 +1,8 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
@ -29,9 +31,11 @@ namespace SixLabors.ImageSharp.Processing
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext Pad(this IImageProcessingContext source, int width, int height, Color color)
{
Size size = source.GetCurrentSize();
var options = new ResizeOptions
{
Size = new Size(width, height),
// Prevent downsizing.
Size = new Size(Math.Max(width, size.Width), Math.Max(height, size.Height)),
Mode = ResizeMode.BoxPad,
Sampler = KnownResamplers.NearestNeighbor,
PadColor = color

2
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs

@ -101,7 +101,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// Returns a <see cref="ResizeKernel"/> for an index value between 0 and DestinationSize - 1.
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
internal ref ResizeKernel GetKernel(int destIdx) => ref this.kernels[destIdx];
internal ref ResizeKernel GetKernel(nint destIdx) => ref this.kernels[destIdx];
/// <summary>
/// Computes the weights to apply at each pixel when resizing.

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

@ -61,9 +61,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
Rectangle destinationRectangle = this.destinationRectangle;
bool compand = this.options.Compand;
bool premultiplyAlpha = this.options.PremultiplyAlpha;
TPixel fillColor = this.options.PadColor.ToPixel<TPixel>();
bool shouldFill = (this.options.Mode == ResizeMode.BoxPad || this.options.Mode == ResizeMode.Pad)
&& this.options.PadColor != default;
TPixel fillColor = this.options.PadColor.ToPixel<TPixel>();
// Handle resize dimensions identical to the original
if (source.Width == destination.Width
@ -209,21 +209,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
// To reintroduce parallel processing, we would launch multiple workers
// for different row intervals of the image.
using (var worker = new ResizeWorker<TPixel>(
using var worker = new ResizeWorker<TPixel>(
configuration,
sourceRegion,
conversionModifiers,
horizontalKernelMap,
verticalKernelMap,
destination.Width,
interest,
destinationRectangle.Location))
{
worker.Initialize();
destinationRectangle.Location);
worker.Initialize();
var workingInterval = new RowInterval(interest.Top, interest.Bottom);
worker.FillDestinationPixels(workingInterval, destination.PixelBuffer);
}
var workingInterval = new RowInterval(interest.Top, interest.Bottom);
worker.FillDestinationPixels(workingInterval, destination.PixelBuffer);
}
private readonly struct NNRowOperation : IRowOperation

27
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs

@ -39,8 +39,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
private readonly ResizeKernelMap verticalKernelMap;
private readonly int destWidth;
private readonly Rectangle targetWorkingRect;
private readonly Point targetOrigin;
@ -57,7 +55,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
PixelConversionModifiers conversionModifiers,
ResizeKernelMap horizontalKernelMap,
ResizeKernelMap verticalKernelMap,
int destWidth,
Rectangle targetWorkingRect,
Point targetOrigin)
{
@ -67,7 +64,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
this.conversionModifiers = conversionModifiers;
this.horizontalKernelMap = horizontalKernelMap;
this.verticalKernelMap = verticalKernelMap;
this.destWidth = destWidth;
this.targetWorkingRect = targetWorkingRect;
this.targetOrigin = targetOrigin;
@ -80,19 +76,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
int numberOfWindowBands = ResizeHelper.CalculateResizeWorkerHeightInWindowBands(
this.windowBandHeight,
destWidth,
targetWorkingRect.Width,
workingBufferLimitHintInBytes);
this.workerHeight = Math.Min(this.sourceRectangle.Height, numberOfWindowBands * this.windowBandHeight);
this.transposedFirstPassBuffer = configuration.MemoryAllocator.Allocate2D<Vector4>(
this.workerHeight,
destWidth,
targetWorkingRect.Width,
preferContiguosImageBuffers: true,
options: AllocationOptions.Clean);
this.tempRowBuffer = configuration.MemoryAllocator.Allocate<Vector4>(this.sourceRectangle.Width);
this.tempColumnBuffer = configuration.MemoryAllocator.Allocate<Vector4>(destWidth);
this.tempColumnBuffer = configuration.MemoryAllocator.Allocate<Vector4>(targetWorkingRect.Width);
this.currentWindow = new RowInterval(0, this.workerHeight);
}
@ -118,6 +114,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
// When creating transposedFirstPassBuffer, we made sure it's contiguous:
Span<Vector4> transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.DangerousGetSingleSpan();
int left = this.targetWorkingRect.Left;
int right = this.targetWorkingRect.Right;
int width = this.targetWorkingRect.Width;
for (int y = rowInterval.Min; y < rowInterval.Max; y++)
{
// Ensure offsets are normalized for cropping and padding.
@ -131,9 +130,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
ref Vector4 tempRowBase = ref MemoryMarshal.GetReference(tempColSpan);
int top = kernel.StartIndex - this.currentWindow.Min;
ref Vector4 fpBase = ref transposedFirstPassBufferSpan[top];
for (int x = 0; x < this.destWidth; x++)
for (nint x = 0; x < (right - left); x++)
{
ref Vector4 firstPassColumnBase = ref Unsafe.Add(ref fpBase, x * this.workerHeight);
@ -141,7 +141,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
Unsafe.Add(ref tempRowBase, x) = kernel.ConvolveCore(ref firstPassColumnBase);
}
Span<TPixel> targetRowSpan = destination.DangerousGetRowSpan(y);
Span<TPixel> targetRowSpan = destination.DangerousGetRowSpan(y).Slice(left, width);
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, tempColSpan, targetRowSpan, this.conversionModifiers);
}
@ -170,6 +170,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
Span<Vector4> tempRowSpan = this.tempRowBuffer.GetSpan();
Span<Vector4> transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.DangerousGetSingleSpan();
int left = this.targetWorkingRect.Left;
int right = this.targetWorkingRect.Right;
int targetOriginX = this.targetOrigin.X;
for (int y = calculationInterval.Min; y < calculationInterval.Max; y++)
{
Span<TPixel> sourceRow = this.source.DangerousGetRowSpan(y);
@ -184,13 +187,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
// Span<Vector4> firstPassSpan = transposedFirstPassBufferSpan.Slice(y - this.currentWindow.Min);
ref Vector4 firstPassBaseRef = ref transposedFirstPassBufferSpan[y - this.currentWindow.Min];
for (int x = this.targetWorkingRect.Left; x < this.targetWorkingRect.Right; x++)
for (nint x = left, z = 0; x < right; x++, z++)
{
ResizeKernel kernel = this.horizontalKernelMap.GetKernel(x - this.targetOrigin.X);
ResizeKernel kernel = this.horizontalKernelMap.GetKernel(x - targetOriginX);
// optimization for:
// firstPassSpan[x * this.workerHeight] = kernel.Convolve(tempRowSpan);
Unsafe.Add(ref firstPassBaseRef, x * this.workerHeight) = kernel.Convolve(tempRowSpan);
Unsafe.Add(ref firstPassBaseRef, z * this.workerHeight) = kernel.Convolve(tempRowSpan);
}
}
}

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

@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
private void GenericBechmark()
{
this.preloadedImageStream.Position = 0;
using Image img = this.decoder.Decode(Configuration.Default, this.preloadedImageStream);
using Image img = this.decoder.Decode(Configuration.Default, this.preloadedImageStream, default);
}
[GlobalSetup(Target = nameof(JpegBaselineInterleaved444))]

134
tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs

@ -1,134 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Drawing.Imaging;
using System.IO;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests;
using SDImage = System.Drawing.Image;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
public class EncodeJpeg
{
[Params(75, 90, 100)]
public int Quality;
private const string TestImage = TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr;
// System.Drawing
private SDImage bmpDrawing;
private Stream bmpStream;
private ImageCodecInfo jpegCodec;
private EncoderParameters encoderParameters;
// ImageSharp
private Image<Rgba32> bmpCore;
private JpegEncoder encoder420;
private JpegEncoder encoder444;
private MemoryStream destinationStream;
[GlobalSetup]
public void ReadImages()
{
if (this.bmpStream == null)
{
this.bmpStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImage));
this.bmpCore = Image.Load<Rgba32>(this.bmpStream);
this.bmpCore.Metadata.ExifProfile = null;
this.encoder420 = new JpegEncoder { Quality = this.Quality, ColorType = JpegColorType.YCbCrRatio420 };
this.encoder444 = new JpegEncoder { Quality = this.Quality, ColorType = JpegColorType.YCbCrRatio444 };
this.bmpStream.Position = 0;
this.bmpDrawing = SDImage.FromStream(this.bmpStream);
this.jpegCodec = GetEncoder(ImageFormat.Jpeg);
this.encoderParameters = new EncoderParameters(1);
// Quality cast to long is necessary
#pragma warning disable IDE0004 // Remove Unnecessary Cast
this.encoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, (long)this.Quality);
#pragma warning restore IDE0004 // Remove Unnecessary Cast
this.destinationStream = new MemoryStream();
}
}
[GlobalCleanup]
public void Cleanup()
{
this.bmpStream.Dispose();
this.bmpStream = null;
this.destinationStream.Dispose();
this.destinationStream = null;
this.bmpCore.Dispose();
this.bmpDrawing.Dispose();
this.encoderParameters.Dispose();
}
[Benchmark(Baseline = true, Description = "System.Drawing Jpeg 4:2:0")]
public void JpegSystemDrawing()
{
this.bmpDrawing.Save(this.destinationStream, this.jpegCodec, this.encoderParameters);
this.destinationStream.Seek(0, SeekOrigin.Begin);
}
[Benchmark(Description = "ImageSharp Jpeg 4:2:0")]
public void JpegCore420()
{
this.bmpCore.SaveAsJpeg(this.destinationStream, this.encoder420);
this.destinationStream.Seek(0, SeekOrigin.Begin);
}
[Benchmark(Description = "ImageSharp Jpeg 4:4:4")]
public void JpegCore444()
{
this.bmpCore.SaveAsJpeg(this.destinationStream, this.encoder444);
this.destinationStream.Seek(0, SeekOrigin.Begin);
}
// https://docs.microsoft.com/en-us/dotnet/api/system.drawing.imaging.encoderparameter?redirectedfrom=MSDN&view=net-5.0
private static ImageCodecInfo GetEncoder(ImageFormat format)
{
ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders();
foreach (ImageCodecInfo codec in codecs)
{
if (codec.FormatID == format.Guid)
{
return codec;
}
}
return null;
}
}
}
/*
BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19042
Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores
.NET SDK=6.0.100-preview.3.21202.5
[Host] : .NET Core 3.1.18 (CoreCLR 4.700.21.35901, CoreFX 4.700.21.36305), X64 RyuJIT
DefaultJob : .NET Core 3.1.18 (CoreCLR 4.700.21.35901, CoreFX 4.700.21.36305), X64 RyuJIT
| Method | Quality | Mean | Error | StdDev | Ratio |
|---------------------------- |-------- |---------:|---------:|---------:|------:|
| 'System.Drawing Jpeg 4:2:0' | 75 | 30.04 ms | 0.540 ms | 0.479 ms | 1.00 |
| 'ImageSharp Jpeg 4:2:0' | 75 | 19.32 ms | 0.290 ms | 0.257 ms | 0.64 |
| 'ImageSharp Jpeg 4:4:4' | 75 | 26.76 ms | 0.332 ms | 0.294 ms | 0.89 |
| | | | | | |
| 'System.Drawing Jpeg 4:2:0' | 90 | 32.82 ms | 0.184 ms | 0.163 ms | 1.00 |
| 'ImageSharp Jpeg 4:2:0' | 90 | 25.00 ms | 0.408 ms | 0.361 ms | 0.76 |
| 'ImageSharp Jpeg 4:4:4' | 90 | 31.83 ms | 0.636 ms | 0.595 ms | 0.97 |
| | | | | | |
| 'System.Drawing Jpeg 4:2:0' | 100 | 39.30 ms | 0.359 ms | 0.318 ms | 1.00 |
| 'ImageSharp Jpeg 4:2:0' | 100 | 34.49 ms | 0.265 ms | 0.235 ms | 0.88 |
| 'ImageSharp Jpeg 4:4:4' | 100 | 56.40 ms | 0.565 ms | 0.501 ms | 1.44 |
*/

112
tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegComparison.cs

@ -0,0 +1,112 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Drawing.Imaging;
using System.IO;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests;
using SkiaSharp;
using SDImage = System.Drawing.Image;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
/// <summary>
/// Benchmark for performance comparison between other codecs.
/// </summary>
/// <remarks>
/// This benchmarks tests baseline 4:2:0 chroma sampling path.
/// </remarks>
public class EncodeJpegComparison
{
// Big enough, 4:4:4 chroma sampling
private const string TestImage = TestImages.Jpeg.Baseline.Calliphora;
// Change/add parameters for extra benchmarks
[Params(75, 90, 100)]
public int Quality;
private MemoryStream destinationStream;
// ImageSharp
private Image<Rgba32> imageImageSharp;
private JpegEncoder encoderImageSharp;
// SkiaSharp
private SKBitmap imageSkiaSharp;
[GlobalSetup(Target = nameof(BenchmarkImageSharp))]
public void SetupImageSharp()
{
using FileStream imageBinaryStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImage));
this.imageImageSharp = Image.Load<Rgba32>(imageBinaryStream);
this.encoderImageSharp = new JpegEncoder { Quality = this.Quality, ColorType = JpegColorType.YCbCrRatio420 };
this.destinationStream = new MemoryStream();
}
[GlobalCleanup(Target = nameof(BenchmarkImageSharp))]
public void CleanupImageSharp()
{
this.imageImageSharp.Dispose();
this.imageImageSharp = null;
this.destinationStream.Dispose();
this.destinationStream = null;
}
[Benchmark(Description = "ImageSharp")]
public void BenchmarkImageSharp()
{
this.imageImageSharp.SaveAsJpeg(this.destinationStream, this.encoderImageSharp);
this.destinationStream.Seek(0, SeekOrigin.Begin);
}
[GlobalSetup(Target = nameof(BenchmarkSkiaSharp))]
public void SetupSkiaSharp()
{
using FileStream imageBinaryStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImage));
this.imageSkiaSharp = SKBitmap.Decode(imageBinaryStream);
this.destinationStream = new MemoryStream();
}
[GlobalCleanup(Target = nameof(BenchmarkSkiaSharp))]
public void CleanupSkiaSharp()
{
this.imageSkiaSharp.Dispose();
this.imageSkiaSharp = null;
this.destinationStream.Dispose();
this.destinationStream = null;
}
[Benchmark(Description = "SkiaSharp")]
public void BenchmarkSkiaSharp()
{
this.imageSkiaSharp.Encode(SKEncodedImageFormat.Jpeg, this.Quality).SaveTo(this.destinationStream);
this.destinationStream.Seek(0, SeekOrigin.Begin);
}
}
}
/*
BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19044
Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores
.NET SDK=6.0.100-preview.3.21202.5
[Host] : .NET Core 3.1.21 (CoreCLR 4.700.21.51404, CoreFX 4.700.21.51508), X64 RyuJIT
DefaultJob : .NET Core 3.1.21 (CoreCLR 4.700.21.51404, CoreFX 4.700.21.51508), X64 RyuJIT
| Method | Quality | Mean | Error | StdDev |
|----------- |-------- |----------:|----------:|----------:|
| ImageSharp | 75 | 6.820 ms | 0.0374 ms | 0.0312 ms |
| SkiaSharp | 75 | 16.417 ms | 0.3238 ms | 0.4747 ms |
| ImageSharp | 90 | 7.849 ms | 0.1565 ms | 0.3126 ms |
| SkiaSharp | 90 | 16.893 ms | 0.2200 ms | 0.2058 ms |
| ImageSharp | 100 | 11.016 ms | 0.2087 ms | 0.1850 ms |
| SkiaSharp | 100 | 20.410 ms | 0.2583 ms | 0.2290 ms |
*/

89
tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegFeatures.cs

@ -0,0 +1,89 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
using System.IO;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
/// <summary>
/// Benchmark for all available encoding features of the Jpeg file type.
/// </summary>
/// <remarks>
/// This benchmark does NOT compare ImageSharp to any other jpeg codecs.
/// </remarks>
public class EncodeJpegFeatures
{
// Big enough, 4:4:4 chroma sampling
// No metadata
private const string TestImage = TestImages.Jpeg.Baseline.Calliphora;
public static IEnumerable<JpegColorType> ColorSpaceValues =>
new[] { JpegColorType.Luminance, JpegColorType.Rgb, JpegColorType.YCbCrRatio420, JpegColorType.YCbCrRatio444 };
[Params(75, 90, 100)]
public int Quality;
[ParamsSource(nameof(ColorSpaceValues), Priority = -100)]
public JpegColorType TargetColorSpace;
private Image<Rgb24> bmpCore;
private JpegEncoder encoder;
private MemoryStream destinationStream;
[GlobalSetup]
public void Setup()
{
using FileStream imageBinaryStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImage));
this.bmpCore = Image.Load<Rgb24>(imageBinaryStream);
this.encoder = new JpegEncoder { Quality = this.Quality, ColorType = this.TargetColorSpace };
this.destinationStream = new MemoryStream();
}
[GlobalCleanup]
public void Cleanup()
{
this.bmpCore.Dispose();
this.bmpCore = null;
this.destinationStream.Dispose();
this.destinationStream = null;
}
[Benchmark]
public void Benchmark()
{
this.bmpCore.SaveAsJpeg(this.destinationStream, this.encoder);
this.destinationStream.Seek(0, SeekOrigin.Begin);
}
}
}
/*
BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19044
Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores
.NET SDK=6.0.100-preview.3.21202.5
[Host] : .NET Core 3.1.21 (CoreCLR 4.700.21.51404, CoreFX 4.700.21.51508), X64 RyuJIT
DefaultJob : .NET Core 3.1.21 (CoreCLR 4.700.21.51404, CoreFX 4.700.21.51508), X64 RyuJIT
| Method | TargetColorSpace | Quality | Mean | Error | StdDev |
|---------- |----------------- |-------- |----------:|----------:|----------:|
| Benchmark | Luminance | 75 | 7.055 ms | 0.1411 ms | 0.3297 ms |
| Benchmark | Rgb | 75 | 12.139 ms | 0.0645 ms | 0.0538 ms |
| Benchmark | YCbCrRatio420 | 75 | 6.463 ms | 0.0282 ms | 0.0235 ms |
| Benchmark | YCbCrRatio444 | 75 | 8.616 ms | 0.0422 ms | 0.0374 ms |
| Benchmark | Luminance | 90 | 7.011 ms | 0.0361 ms | 0.0301 ms |
| Benchmark | Rgb | 90 | 13.119 ms | 0.0947 ms | 0.0886 ms |
| Benchmark | YCbCrRatio420 | 90 | 6.786 ms | 0.0328 ms | 0.0274 ms |
| Benchmark | YCbCrRatio444 | 90 | 8.672 ms | 0.0772 ms | 0.0722 ms |
| Benchmark | Luminance | 100 | 9.554 ms | 0.1211 ms | 0.1012 ms |
| Benchmark | Rgb | 100 | 19.475 ms | 0.1080 ms | 0.0958 ms |
| Benchmark | YCbCrRatio420 | 100 | 10.146 ms | 0.0585 ms | 0.0519 ms |
| Benchmark | YCbCrRatio444 | 100 | 15.317 ms | 0.0709 ms | 0.0592 ms |
*/

34
tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegMultiple.cs

@ -1,34 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
using System.Drawing.Imaging;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
[Config(typeof(Config.ShortMultiFramework))]
public class EncodeJpegMultiple : MultiImageBenchmarkBase.WithImagesPreloaded
{
protected override IEnumerable<string> InputImageSubfoldersOrFiles => new[] { "Bmp/", "Jpg/baseline" };
protected override IEnumerable<string> SearchPatterns => new[] { "*.bmp", "*.jpg" };
[Benchmark(Description = "EncodeJpegMultiple - ImageSharp")]
public void EncodeJpegImageSharp()
=> this.ForEachImageSharpImage((img, ms) =>
{
img.Save(ms, new JpegEncoder());
return null;
});
[Benchmark(Baseline = true, Description = "EncodeJpegMultiple - System.Drawing")]
public void EncodeJpegSystemDrawing()
=> this.ForEachSystemDrawingImage((img, ms) =>
{
img.Save(ms, ImageFormat.Jpeg);
return null;
});
}
}

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

@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
using var memoryStream = new MemoryStream(this.jpegBytes);
var decoder = new JpegDecoder();
return decoder.Identify(Configuration.Default, memoryStream);
return decoder.Identify(Configuration.Default, memoryStream, default);
}
}
}

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

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
@ -9,6 +10,7 @@ using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using ImageMagick;
using PhotoSauce.MagicScaler;
@ -124,6 +126,32 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave
new ParallelOptions { MaxDegreeOfParallelism = this.MaxDegreeOfParallelism },
action);
public Task ForEachImageParallelAsync(Func<string, Task> action)
{
int maxDegreeOfParallelism = this.MaxDegreeOfParallelism > 0
? this.MaxDegreeOfParallelism
: Environment.ProcessorCount;
int partitionSize = (int)Math.Ceiling((double)this.Images.Length / maxDegreeOfParallelism);
List<Task> tasks = new();
for (int i = 0; i < this.Images.Length; i += partitionSize)
{
int end = Math.Min(i + partitionSize, this.Images.Length);
Task task = RunPartition(i, end);
tasks.Add(task);
}
return Task.WhenAll(tasks);
Task RunPartition(int start, int end) => Task.Run(async () =>
{
for (int i = start; i < end; i++)
{
await action(this.Images[i]);
}
});
}
private void LogImageProcessed(int width, int height)
{
this.LastProcessedImageSize = new Size(width, height);
@ -197,6 +225,26 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave
image.Save(output, this.imageSharpJpegEncoder);
}
public async Task ImageSharpResizeAsync(string input)
{
using FileStream output = File.Open(this.OutputPath(input), FileMode.Create);
// Resize it to fit a 150x150 square
using var image = await ImageSharpImage.LoadAsync(input);
this.LogImageProcessed(image.Width, image.Height);
image.Mutate(i => i.Resize(new ResizeOptions
{
Size = new ImageSharpSize(this.ThumbnailSize, this.ThumbnailSize),
Mode = ResizeMode.Max
}));
// Reduce the size of the file
image.Metadata.ExifProfile = null;
// Save the results
await image.SaveAsync(output, this.imageSharpJpegEncoder);
}
public void MagickResize(string input)
{
using var image = new MagickImage(input);

24
tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs

@ -51,7 +51,7 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox
Console.WriteLine($"\nEnvironment.ProcessorCount={Environment.ProcessorCount}");
Stopwatch timer;
if (options == null || !options.ImageSharp)
if (options == null || !(options.ImageSharp || options.AsyncImageSharp))
{
RunBenchmarkSwitcher(lrs, out timer);
}
@ -74,7 +74,16 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox
{
for (int i = 0; i < options.RepeatCount; i++)
{
lrs.ImageSharpBenchmarkParallel();
if (options.AsyncImageSharp)
{
lrs.ImageSharpBenchmarkParallelAsync();
}
else
{
lrs.ImageSharpBenchmarkParallel();
}
Console.WriteLine("OK");
}
}
catch (Exception ex)
@ -221,6 +230,9 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox
private class CommandLineOptions
{
[Option('a', "async-imagesharp", Required = false, Default = false, HelpText = "Async ImageSharp without benchmark switching")]
public bool AsyncImageSharp { get; set; }
[Option('i', "imagesharp", Required = false, Default = false, HelpText = "Test ImageSharp without benchmark switching")]
public bool ImageSharp { get; set; }
@ -277,7 +289,7 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox
}
public override string ToString() =>
$"p({this.MaxDegreeOfParallelism})_i({this.ImageSharp})_d({this.KeepDefaultAllocator})_m({this.MaxContiguousPoolBufferMegaBytes})_s({this.MaxPoolSizeMegaBytes})_u({this.MaxCapacityOfNonPoolBuffersMegaBytes})_r({this.RepeatCount})_g({this.GcFrequency})_e({this.ReleaseRetainedResourcesAtEnd})_l({this.LeakFrequency})";
$"p({this.MaxDegreeOfParallelism})_i({this.ImageSharp})_a({this.AsyncImageSharp})_d({this.KeepDefaultAllocator})_m({this.MaxContiguousPoolBufferMegaBytes})_s({this.MaxPoolSizeMegaBytes})_u({this.MaxCapacityOfNonPoolBuffersMegaBytes})_r({this.RepeatCount})_g({this.GcFrequency})_e({this.ReleaseRetainedResourcesAtEnd})_l({this.LeakFrequency})";
public MemoryAllocator CreateMemoryAllocator()
{
@ -330,11 +342,17 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox
}
});
private void ImageSharpBenchmarkParallelAsync() =>
this.Benchmarks.ForEachImageParallelAsync(f => this.Benchmarks.ImageSharpResizeAsync(f))
.GetAwaiter()
.GetResult();
private void MagickBenchmarkParallel() => this.ForEachImage(this.Benchmarks.MagickResize);
private void MagicScalerBenchmarkParallel() => this.ForEachImage(this.Benchmarks.MagicScalerResize);
private void SkiaBitmapBenchmarkParallel() => this.ForEachImage(this.Benchmarks.SkiaBitmapResize);
private void SkiaBitmapDecodeToTargetSizeBenchmarkParallel() => this.ForEachImage(this.Benchmarks.SkiaBitmapDecodeToTargetSize);
private void NetVipsBenchmarkParallel() => this.ForEachImage(this.Benchmarks.NetVipsResize);

5
tests/ImageSharp.Tests.ruleset

@ -1,6 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<RuleSet Name="ImageSharp.Tests" ToolsVersion="16.0">
<RuleSet Name="ImageSharp.Tests" ToolsVersion="17.0">
<Include Path="..\shared-infrastructure\sixlabors.tests.ruleset" Action="Default" />
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.Analyzers">
<Rule Id="SA1313" Action="None" />
</Rules>
<Rules AnalyzerId="xunit.analyzers" RuleNamespace="xunit.analyzers">
<Rule Id="xUnit1004" Action="None" />
<Rule Id="xUnit1013" Action="None" />

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

@ -557,7 +557,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
using (var stream = new MemoryStream(testFile.Bytes, false))
{
var decoder = new BmpDecoder();
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, stream))
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, stream, default))
{
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);

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

@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
{
using (var stream = new UnmanagedMemoryStream(data, length))
{
using (Image<Rgba32> image = GifDecoder.Decode<Rgba32>(Configuration.Default, stream))
using (Image<Rgba32> image = GifDecoder.Decode<Rgba32>(Configuration.Default, stream, default))
{
Assert.Equal((200, 200), (image.Width, image.Height));
}

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

@ -117,7 +117,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
input.Save(memoryStream, new GifEncoder());
memoryStream.Position = 0;
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, memoryStream))
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, memoryStream, default))
{
GifMetadata metadata = image.Metadata.GetGifMetadata();
Assert.Equal(2, metadata.Comments.Count);
@ -135,7 +135,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
using (var stream = new MemoryStream(testFile.Bytes, false))
{
var decoder = new GifDecoder();
IImageInfo image = decoder.Identify(Configuration.Default, stream);
IImageInfo image = decoder.Identify(Configuration.Default, stream, default);
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
@ -151,7 +151,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
using (var stream = new MemoryStream(testFile.Bytes, false))
{
var decoder = new GifDecoder();
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, stream))
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, stream, default))
{
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
@ -169,7 +169,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
using (var stream = new MemoryStream(testFile.Bytes, false))
{
var decoder = new GifDecoder();
IImageInfo image = decoder.Identify(Configuration.Default, stream);
IImageInfo image = decoder.Identify(Configuration.Default, stream, default);
GifMetadata meta = image.Metadata.GetGifMetadata();
Assert.Equal(repeatCount, meta.RepeatCount);
}
@ -183,7 +183,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
using (var stream = new MemoryStream(testFile.Bytes, false))
{
var decoder = new GifDecoder();
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, stream))
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, stream, default))
{
GifMetadata meta = image.Metadata.GetGifMetadata();
Assert.Equal(repeatCount, meta.RepeatCount);

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

@ -4,7 +4,6 @@
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Metadata;
@ -84,7 +83,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
using (var stream = new MemoryStream(testFile.Bytes, false))
{
var decoder = new JpegDecoder();
using (Image image = decoder.Decode(Configuration.Default, stream))
using (Image image = decoder.Decode(Configuration.Default, stream, default))
{
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
@ -102,7 +101,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
using (var stream = new MemoryStream(testFile.Bytes, false))
{
var decoder = new JpegDecoder();
IImageInfo image = decoder.Identify(Configuration.Default, stream);
IImageInfo image = decoder.Identify(Configuration.Default, stream, default);
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
@ -118,7 +117,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
using (var stream = new MemoryStream(testFile.Bytes, false))
{
var decoder = new JpegDecoder();
IImageInfo image = decoder.Identify(Configuration.Default, stream);
IImageInfo image = decoder.Identify(Configuration.Default, stream, default);
JpegMetadata meta = image.Metadata.GetJpegMetadata();
Assert.Equal(quality, meta.Quality);
}
@ -131,7 +130,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
using (Image image = JpegDecoder.Decode(Configuration.Default, stream))
using (Image image = JpegDecoder.Decode(Configuration.Default, stream, default))
{
JpegMetadata meta = image.Metadata.GetJpegMetadata();
Assert.Equal(quality, meta.Quality);
@ -153,7 +152,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
IImageInfo image = JpegDecoder.Identify(Configuration.Default, stream);
IImageInfo image = JpegDecoder.Identify(Configuration.Default, stream, default);
JpegMetadata meta = image.Metadata.GetJpegMetadata();
Assert.Equal(expectedColorType, meta.ColorType);
}
@ -181,8 +180,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
using (var stream = new MemoryStream(testFile.Bytes, false))
{
IImageInfo imageInfo = useIdentify
? ((IImageInfoDetector)decoder).Identify(Configuration.Default, stream)
: decoder.Decode<Rgba32>(Configuration.Default, stream);
? ((IImageInfoDetector)decoder).Identify(Configuration.Default, stream, default)
: decoder.Decode<Rgba32>(Configuration.Default, stream, default);
test(imageInfo);
}
@ -302,6 +301,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Assert.Null(ex);
}
[Theory]
[WithFile(TestImages.Jpeg.Issues.ExifNullArrayTag, PixelTypes.Rgba32)]
public void Clone_WithNullRationalArrayTag_DoesNotThrowException<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
Exception ex = Record.Exception(() =>
{
using Image<TPixel> image = provider.GetImage(JpegDecoder);
var clone = image.Metadata.ExifProfile.DeepClone();
});
Assert.Null(ex);
}
[Fact]
public void EncodedStringTags_WriteAndRead()
{

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

@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
private ITestOutputHelper Output { get; }
private static JpegDecoder JpegDecoder => new JpegDecoder();
private static JpegDecoder JpegDecoder => new();
[Fact]
public void ParseStream_BasicPropertiesAreCorrect()
@ -139,27 +139,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Assert.IsType<InvalidMemoryOperationException>(ex.InnerException);
}
[Theory]
[InlineData(0)]
[InlineData(0.5)]
[InlineData(0.9)]
public async Task DecodeAsync_IsCancellable(int percentageOfStreamReadToCancel)
[Fact]
public async Task DecodeAsync_IsCancellable()
{
var cts = new CancellationTokenSource();
string file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.Jpeg420Small);
using var pausedStream = new PausedStream(file);
pausedStream.OnWaiting(s =>
{
if (s.Position >= s.Length * percentageOfStreamReadToCancel)
{
cts.Cancel();
pausedStream.Release();
}
else
{
// allows this/next wait to unblock
pausedStream.Next();
}
cts.Cancel();
pausedStream.Release();
});
var config = Configuration.CreateDefaultInstance();
@ -213,6 +202,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
}
}
// https://github.com/SixLabors/ImageSharp/issues/2057
[Theory]
[WithFile(TestImages.Jpeg.Issues.Issue2057App1Parsing, PixelTypes.Rgba32)]
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);
}
}
// DEBUG ONLY!
// The PDF.js output should be saved by "tests\ImageSharp.Tests\Formats\Jpg\pdfjs\jpeg-converter.htm"
// into "\tests\Images\ActualOutput\JpegDecoderTests\"

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

@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
var decoder = new PngDecoder();
ImageFormatException exception =
Assert.Throws<InvalidImageContentException>(() => decoder.Decode<Rgb24>(Configuration.Default, memStream));
Assert.Throws<InvalidImageContentException>(() => decoder.Decode<Rgb24>(Configuration.Default, memStream, default));
Assert.Equal($"CRC Error. PNG {chunkName} chunk is corrupt!", exception.Message);
}

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

@ -292,7 +292,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
var decoder = new PngDecoder();
Image image = decoder.Decode(Configuration.Default, stream);
Image image = decoder.Decode(Configuration.Default, stream, default);
PngMetadata metadata = image.Metadata.GetPngMetadata();
Assert.Equal(pngColorType, metadata.ColorType);

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

@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
input.Save(memoryStream, new PngEncoder());
memoryStream.Position = 0;
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, memoryStream))
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, memoryStream, default))
{
PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance);
VerifyTextDataIsPresent(meta);
@ -123,7 +123,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
});
memoryStream.Position = 0;
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, memoryStream))
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));
@ -212,7 +212,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
using (var stream = new MemoryStream(testFile.Bytes, false))
{
var decoder = new PngDecoder();
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, stream))
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, stream, default))
{
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
@ -230,7 +230,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
using (var stream = new MemoryStream(testFile.Bytes, false))
{
var decoder = new PngDecoder();
IImageInfo image = decoder.Identify(Configuration.Default, stream);
IImageInfo image = decoder.Identify(Configuration.Default, stream, default);
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);

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

@ -78,9 +78,42 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
[Theory]
[WithFile(FlowerRgb888Planar6Strips, PixelTypes.Rgba32)]
[WithFile(FlowerRgb888Planar15Strips, PixelTypes.Rgba32)]
public void TiffDecoder_Planar<TPixel>(TestImageProvider<TPixel> provider)
public void TiffDecoder_CanDecode_Planar<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Rgba8BitPlanarUnassociatedAlpha, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_Planar_32Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Rgba16BitPlanarUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)]
[WithFile(Rgba16BitPlanarUnassociatedAlphaBigEndian, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_Planar_64Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Rgba24BitPlanarUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)]
[WithFile(Rgba24BitPlanarUnassociatedAlphaBigEndian, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_Planar_96Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
// Note: because the MagickReferenceDecoder fails to load the image, we only debug save them.
using Image<TPixel> image = provider.GetImage();
image.DebugSave(provider);
}
[Theory]
[WithFile(Rgba32BitPlanarUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)]
[WithFile(Rgba32BitPlanarUnassociatedAlphaBigEndian, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_Planar_128Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
// Note: because the MagickReferenceDecoder fails to load the image, we only debug save them.
using Image<TPixel> image = provider.GetImage();
image.DebugSave(provider);
}
[Theory]
[WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)]
[WithFile(PaletteDeflateMultistrip, PixelTypes.Rgba32)]
@ -123,6 +156,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public void TiffDecoder_CanDecode_8Bit_Gray<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Rgba2BitUnassociatedAlpha, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_8Bit_WithUnassociatedAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(FLowerRgb3Bit, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_9Bit_WithUnassociatedAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Flower10BitGray, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_10Bit_Gray<TPixel>(TestImageProvider<TPixel> provider)
@ -139,11 +182,21 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public void TiffDecoder_CanDecode_12Bit_Gray<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Rgba3BitUnassociatedAlpha, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_12Bit_WithUnassociatedAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Flower14BitGray, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_14Bit_Gray<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(FLowerRgb5Bit, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_15Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Flower16BitGrayLittleEndian, PixelTypes.Rgba32)]
[WithFile(Flower16BitGray, PixelTypes.Rgba32)]
@ -158,6 +211,31 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public void TiffDecoder_CanDecode_16Bit_Gray_WithPredictor<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Rgba4BitUnassociatedAlpha, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_16Bit_WithUnassociatedAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(FLowerRgb6Bit, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_18Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Rgba5BitUnassociatedAlpha, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_20Bit_WithUnassociatedAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(FlowerRgb888Contiguous, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_24Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Rgba6BitUnassociatedAlpha, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_24Bit_WithUnassociatedAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Flower24BitGray, PixelTypes.Rgba32)]
[WithFile(Flower24BitGrayLittleEndian, PixelTypes.Rgba32)]
@ -207,6 +285,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
image.DebugSave(provider);
}
[Theory]
[WithFile(Rgba8BitUnassociatedAlpha, PixelTypes.Rgba32)]
[WithFile(Rgba8BitUnassociatedAlphaWithPredictor, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_32Bit_WithUnassociatedAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
// Note: because the MagickReferenceDecoder fails to load the image, we only debug save them.
using Image<TPixel> image = provider.GetImage();
image.DebugSave(provider);
}
[Theory]
[WithFile(Flower32BitGrayPredictorBigEndian, PixelTypes.Rgba32)]
[WithFile(Flower32BitGrayPredictorLittleEndian, PixelTypes.Rgba32)]
@ -223,6 +312,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public void TiffDecoder_CanDecode_36Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Rgba10BitUnassociatedAlphaBigEndian, PixelTypes.Rgba32)]
[WithFile(Rgba10BitUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_40Bit_WithUnassociatedAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(FlowerRgb141414Contiguous, PixelTypes.Rgba32)]
[WithFile(FlowerRgb141414Planar, PixelTypes.Rgba32)]
@ -238,12 +333,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public void TiffDecoder_CanDecode_48Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Rgba12BitUnassociatedAlphaBigEndian, PixelTypes.Rgba32)]
[WithFile(Rgba12BitUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_48Bit_WithUnassociatedAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(FlowerRgb161616PredictorBigEndian, PixelTypes.Rgba32)]
[WithFile(FlowerRgb161616PredictorLittleEndian, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_48Bit_WithPredictor<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Rgba14BitUnassociatedAlphaBigEndian, PixelTypes.Rgba32)]
[WithFile(Rgba14BitUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_56Bit_WithUnassociatedAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(FlowerRgb242424Contiguous, PixelTypes.Rgba32)]
[WithFile(FlowerRgb242424ContiguousLittleEndian, PixelTypes.Rgba32)]
@ -270,6 +377,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
image.DebugSave(provider);
}
[Theory]
[WithFile(Rgba24BitUnassociatedAlphaBigEndian, PixelTypes.Rgba32)]
[WithFile(Rgba24BitUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_96Bit_WithUnassociatedAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
// Note: because the MagickReferenceDecoder fails to load the image, we only debug save them.
using Image<TPixel> image = provider.GetImage();
image.DebugSave(provider);
}
[Theory]
[WithFile(FlowerRgbFloat323232, PixelTypes.Rgba32)]
[WithFile(FlowerRgbFloat323232LittleEndian, PixelTypes.Rgba32)]
@ -305,6 +423,27 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
image.DebugSave(provider);
}
[Theory]
[WithFile(Rgba16BitUnassociatedAlphaBigEndian, PixelTypes.Rgba32)]
[WithFile(Rgba16BitUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)]
[WithFile(Rgba16BitUnassociatedAlphaBigEndianWithPredictor, PixelTypes.Rgba32)]
[WithFile(Rgba16BitUnassociatedAlphaLittleEndianWithPredictor, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_128Bit_UnassociatedAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Rgba32BitUnassociatedAlphaBigEndian, PixelTypes.Rgba32)]
[WithFile(Rgba32BitUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)]
[WithFile(Rgba32BitUnassociatedAlphaBigEndianWithPredictor, PixelTypes.Rgba32)]
[WithFile(Rgba32BitUnassociatedAlphaLittleEndianWithPredictor, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_128Bit_WithUnassociatedAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
// Note: because the MagickReferenceDecoder fails to load the image, we only debug save them.
using Image<TPixel> image = provider.GetImage();
image.DebugSave(provider);
}
[Theory]
[WithFile(GrayscaleDeflateMultistrip, PixelTypes.Rgba32)]
[WithFile(RgbDeflateMultistrip, PixelTypes.Rgba32)]

80
tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs

@ -18,19 +18,19 @@ namespace SixLabors.ImageSharp.Tests.IO
/// </summary>
public class ChunkedMemoryStreamTests
{
private readonly MemoryAllocator allocator;
/// <summary>
/// The default length in bytes of each buffer chunk when allocating large buffers.
/// </summary>
private const int DefaultLargeChunkSize = 1024 * 1024 * 4; // 4 Mb
public ChunkedMemoryStreamTests()
{
this.allocator = Configuration.Default.MemoryAllocator;
}
/// <summary>
/// The default length in bytes of each buffer chunk when allocating small buffers.
/// </summary>
private const int DefaultSmallChunkSize = DefaultLargeChunkSize / 32; // 128 Kb
[Fact]
public void MemoryStream_Ctor_InvalidCapacities()
{
Assert.Throws<ArgumentOutOfRangeException>(() => new ChunkedMemoryStream(int.MinValue, this.allocator));
Assert.Throws<ArgumentOutOfRangeException>(() => new ChunkedMemoryStream(0, this.allocator));
}
private readonly MemoryAllocator allocator;
public ChunkedMemoryStreamTests() => this.allocator = Configuration.Default.MemoryAllocator;
[Fact]
public void MemoryStream_GetPositionTest_Negative()
@ -61,11 +61,11 @@ namespace SixLabors.ImageSharp.Tests.IO
}
[Theory]
[InlineData(ChunkedMemoryStream.DefaultBufferLength)]
[InlineData((int)(ChunkedMemoryStream.DefaultBufferLength * 1.5))]
[InlineData(ChunkedMemoryStream.DefaultBufferLength * 4)]
[InlineData((int)(ChunkedMemoryStream.DefaultBufferLength * 5.5))]
[InlineData(ChunkedMemoryStream.DefaultBufferLength * 8)]
[InlineData(DefaultSmallChunkSize)]
[InlineData((int)(DefaultSmallChunkSize * 1.5))]
[InlineData(DefaultSmallChunkSize * 4)]
[InlineData((int)(DefaultSmallChunkSize * 5.5))]
[InlineData(DefaultSmallChunkSize * 16)]
public void MemoryStream_ReadByteTest(int length)
{
using MemoryStream ms = this.CreateTestStream(length);
@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Tests.IO
ms.CopyTo(cms);
cms.Position = 0;
var expected = ms.ToArray();
byte[] expected = ms.ToArray();
for (int i = 0; i < expected.Length; i++)
{
@ -82,11 +82,11 @@ namespace SixLabors.ImageSharp.Tests.IO
}
[Theory]
[InlineData(ChunkedMemoryStream.DefaultBufferLength)]
[InlineData((int)(ChunkedMemoryStream.DefaultBufferLength * 1.5))]
[InlineData(ChunkedMemoryStream.DefaultBufferLength * 4)]
[InlineData((int)(ChunkedMemoryStream.DefaultBufferLength * 5.5))]
[InlineData(ChunkedMemoryStream.DefaultBufferLength * 8)]
[InlineData(DefaultSmallChunkSize)]
[InlineData((int)(DefaultSmallChunkSize * 1.5))]
[InlineData(DefaultSmallChunkSize * 4)]
[InlineData((int)(DefaultSmallChunkSize * 5.5))]
[InlineData(DefaultSmallChunkSize * 16)]
public void MemoryStream_ReadByteBufferTest(int length)
{
using MemoryStream ms = this.CreateTestStream(length);
@ -94,8 +94,8 @@ namespace SixLabors.ImageSharp.Tests.IO
ms.CopyTo(cms);
cms.Position = 0;
var expected = ms.ToArray();
var buffer = new byte[2];
byte[] expected = ms.ToArray();
byte[] buffer = new byte[2];
for (int i = 0; i < expected.Length; i += 2)
{
cms.Read(buffer);
@ -105,11 +105,11 @@ namespace SixLabors.ImageSharp.Tests.IO
}
[Theory]
[InlineData(ChunkedMemoryStream.DefaultBufferLength)]
[InlineData((int)(ChunkedMemoryStream.DefaultBufferLength * 1.5))]
[InlineData(ChunkedMemoryStream.DefaultBufferLength * 4)]
[InlineData((int)(ChunkedMemoryStream.DefaultBufferLength * 5.5))]
[InlineData(ChunkedMemoryStream.DefaultBufferLength * 8)]
[InlineData(DefaultSmallChunkSize)]
[InlineData((int)(DefaultSmallChunkSize * 1.5))]
[InlineData(DefaultSmallChunkSize * 4)]
[InlineData((int)(DefaultSmallChunkSize * 5.5))]
[InlineData(DefaultSmallChunkSize * 16)]
public void MemoryStream_ReadByteBufferSpanTest(int length)
{
using MemoryStream ms = this.CreateTestStream(length);
@ -117,7 +117,7 @@ namespace SixLabors.ImageSharp.Tests.IO
ms.CopyTo(cms);
cms.Position = 0;
var expected = ms.ToArray();
byte[] expected = ms.ToArray();
Span<byte> buffer = new byte[2];
for (int i = 0; i < expected.Length; i += 2)
{
@ -257,24 +257,24 @@ namespace SixLabors.ImageSharp.Tests.IO
public void MemoryStream_CopyTo_Invalid()
{
ChunkedMemoryStream memoryStream;
const string BufferSize = "bufferSize";
const string bufferSize = nameof(bufferSize);
using (memoryStream = new ChunkedMemoryStream(this.allocator))
{
const string Destination = "destination";
Assert.Throws<ArgumentNullException>(Destination, () => memoryStream.CopyTo(destination: null));
const string destination = nameof(destination);
Assert.Throws<ArgumentNullException>(destination, () => memoryStream.CopyTo(destination: null));
// Validate the destination parameter first.
Assert.Throws<ArgumentNullException>(Destination, () => memoryStream.CopyTo(destination: null, bufferSize: 0));
Assert.Throws<ArgumentNullException>(Destination, () => memoryStream.CopyTo(destination: null, bufferSize: -1));
Assert.Throws<ArgumentNullException>(destination, () => memoryStream.CopyTo(destination: null, bufferSize: 0));
Assert.Throws<ArgumentNullException>(destination, () => memoryStream.CopyTo(destination: null, bufferSize: -1));
// Then bufferSize.
Assert.Throws<ArgumentOutOfRangeException>(BufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: 0)); // 0-length buffer doesn't make sense.
Assert.Throws<ArgumentOutOfRangeException>(BufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: -1));
Assert.Throws<ArgumentOutOfRangeException>(bufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: 0)); // 0-length buffer doesn't make sense.
Assert.Throws<ArgumentOutOfRangeException>(bufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: -1));
}
// After the Stream is disposed, we should fail on all CopyTos.
Assert.Throws<ArgumentOutOfRangeException>(BufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: 0)); // Not before bufferSize is validated.
Assert.Throws<ArgumentOutOfRangeException>(BufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: -1));
Assert.Throws<ArgumentOutOfRangeException>(bufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: 0)); // Not before bufferSize is validated.
Assert.Throws<ArgumentOutOfRangeException>(bufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: -1));
ChunkedMemoryStream disposedStream = memoryStream;
@ -369,7 +369,7 @@ namespace SixLabors.ImageSharp.Tests.IO
private MemoryStream CreateTestStream(int length)
{
var buffer = new byte[length];
byte[] buffer = new byte[length];
var random = new Random();
random.NextBytes(buffer);

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

@ -63,12 +63,11 @@ namespace SixLabors.ImageSharp.Tests
this.localImageFormatMock = new Mock<IImageFormat>();
var detector = new Mock<IImageInfoDetector>();
detector.Setup(x => x.Identify(It.IsAny<Configuration>(), It.IsAny<Stream>())).Returns(this.localImageInfoMock.Object);
detector.Setup(x => x.IdentifyAsync(It.IsAny<Configuration>(), It.IsAny<Stream>(), It.IsAny<CancellationToken>())).ReturnsAsync(this.localImageInfoMock.Object);
detector.Setup(x => x.Identify(It.IsAny<Configuration>(), It.IsAny<Stream>(), It.IsAny<CancellationToken>())).Returns(this.localImageInfoMock.Object);
this.localDecoder = detector.As<IImageDecoder>();
this.localDecoder.Setup(x => x.Decode<Rgba32>(It.IsAny<Configuration>(), It.IsAny<Stream>()))
.Callback<Configuration, Stream>((c, s) =>
this.localDecoder.Setup(x => x.Decode<Rgba32>(It.IsAny<Configuration>(), It.IsAny<Stream>(), It.IsAny<CancellationToken>()))
.Callback<Configuration, Stream, CancellationToken>((c, s, ct) =>
{
using (var ms = new MemoryStream())
{
@ -78,8 +77,8 @@ namespace SixLabors.ImageSharp.Tests
})
.Returns(this.localStreamReturnImageRgba32);
this.localDecoder.Setup(x => x.Decode(It.IsAny<Configuration>(), It.IsAny<Stream>()))
.Callback<Configuration, Stream>((c, s) =>
this.localDecoder.Setup(x => x.Decode(It.IsAny<Configuration>(), It.IsAny<Stream>(), It.IsAny<CancellationToken>()))
.Callback<Configuration, Stream, CancellationToken>((c, s, ct) =>
{
using (var ms = new MemoryStream())
{

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

@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Tests
var img = Image.Load<Rgba32>(this.TopLevelConfiguration, this.MockFilePath, this.localDecoder.Object);
Assert.NotNull(img);
this.localDecoder.Verify(x => x.Decode<Rgba32>(this.TopLevelConfiguration, this.DataStream));
this.localDecoder.Verify(x => x.Decode<Rgba32>(this.TopLevelConfiguration, this.DataStream, default));
}
[Fact]
@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Tests
var img = Image.Load(this.TopLevelConfiguration, this.MockFilePath, this.localDecoder.Object);
Assert.NotNull(img);
this.localDecoder.Verify(x => x.Decode(this.TopLevelConfiguration, this.DataStream));
this.localDecoder.Verify(x => x.Decode(this.TopLevelConfiguration, this.DataStream, default));
}
[Fact]

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

@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Tests
var img = Image.Load<Rgba32>(this.TopLevelConfiguration, stream, this.localDecoder.Object);
Assert.NotNull(img);
this.localDecoder.Verify(x => x.Decode<Rgba32>(this.TopLevelConfiguration, stream));
this.localDecoder.Verify(x => x.Decode<Rgba32>(this.TopLevelConfiguration, stream, default));
}
[Fact]
@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.Tests
var img = Image.Load(this.TopLevelConfiguration, stream, this.localDecoder.Object);
Assert.NotNull(img);
this.localDecoder.Verify(x => x.Decode(this.TopLevelConfiguration, stream));
this.localDecoder.Verify(x => x.Decode(this.TopLevelConfiguration, stream, default));
}
[Fact]

61
tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs

@ -320,30 +320,51 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
(s, d) => this.Operations.ToVector4(this.Configuration, s, d.GetSpan()));
}
public static readonly TheoryData<object> Generic_To_Data = new TheoryData<object>
public static readonly TheoryData<object> Generic_To_Data = new()
{
new TestPixel<A8>(),
new TestPixel<Abgr32>(),
new TestPixel<Rgba32>(),
new TestPixel<Argb32>(),
new TestPixel<Bgr24>(),
new TestPixel<Bgr565>(),
new TestPixel<Bgra32>(),
new TestPixel<Rgb24>(),
new TestPixel<L8>(),
new TestPixel<Bgra4444>(),
new TestPixel<Bgra5551>(),
new TestPixel<Byte4>(),
new TestPixel<HalfSingle>(),
new TestPixel<HalfVector2>(),
new TestPixel<HalfVector4>(),
new TestPixel<L16>(),
new TestPixel<L8>(),
new TestPixel<La16>(),
new TestPixel<La32>(),
new TestPixel<NormalizedByte2>(),
new TestPixel<NormalizedByte4>(),
new TestPixel<NormalizedShort2>(),
new TestPixel<NormalizedShort4>(),
new TestPixel<Rg32>(),
new TestPixel<Rgb24>(),
new TestPixel<Rgb48>(),
new TestPixel<Rgba64>()
new TestPixel<Rgba1010102>(),
new TestPixel<Rgba32>(),
new TestPixel<Rgba64>(),
new TestPixel<RgbaVector>(),
new TestPixel<Short2>(),
new TestPixel<Short4>(),
};
[Theory]
[MemberData(nameof(Generic_To_Data))]
public void Generic_To<TDestPixel>(TestPixel<TDestPixel> dummy)
public void Generic_To<TDestPixel>(TestPixel<TDestPixel> _)
where TDestPixel : unmanaged, IPixel<TDestPixel>
{
const int Count = 2134;
TPixel[] source = CreatePixelTestData(Count);
var expected = new TDestPixel[Count];
const int count = 2134;
TPixel[] source = CreatePixelTestData(count);
var expected = new TDestPixel[count];
PixelConverterTests.ReferenceImplementations.To<TPixel, TDestPixel>(this.Configuration, source, expected);
TestOperation(source, expected, (s, d) => this.Operations.To(this.Configuration, s, d.GetSpan()));
TestOperation(source, expected, (s, d) => this.Operations.To(this.Configuration, s, d.GetSpan()), false);
}
[Theory]
@ -1234,23 +1255,11 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
}
// TODO: We really need a PixelTypeInfo.BitsPerComponent property!!
private static bool IsComplexPixel()
private static bool IsComplexPixel() => default(TDest) switch
{
switch (default(TDest))
{
case HalfSingle _:
case HalfVector2 _:
case L16 _:
case La32 _:
case NormalizedShort2 _:
case Rg32 _:
case Short2 _:
return true;
default:
return Unsafe.SizeOf<TDest>() > sizeof(int);
}
}
HalfSingle or HalfVector2 or L16 or La32 or NormalizedShort2 or Rg32 or Short2 => true,
_ => Unsafe.SizeOf<TDest>() > sizeof(int),
};
}
}
}

16
tests/ImageSharp.Tests/PixelFormats/RgbaVectorTests.cs

@ -189,5 +189,21 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats
// assert
Assert.Equal(expected, rgba.ToScaledVector4());
}
[Fact]
public void Issue2048()
{
// https://github.com/SixLabors/ImageSharp/issues/2048
RgbaVector green = Color.Green.ToPixel<RgbaVector>();
using Image<RgbaVector> source = new(Configuration.Default, 1, 1, green);
using Image<HalfVector4> clone = source.CloneAs<HalfVector4>();
Rgba32 srcColor = default;
Rgba32 cloneColor = default;
source[0, 0].ToRgba32(ref srcColor);
clone[0, 0].ToRgba32(ref cloneColor);
Assert.Equal(srcColor, cloneColor);
}
}
}

6
tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs

@ -472,7 +472,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
var options = new ResizeOptions
{
Size = new Size(image.Width + 200, image.Height + 200),
Mode = ResizeMode.BoxPad
Mode = ResizeMode.BoxPad,
PadColor = Color.HotPink
};
image.Mutate(x => x.Resize(options));
@ -580,7 +581,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
var options = new ResizeOptions
{
Size = new Size(image.Width + 200, image.Height),
Mode = ResizeMode.Pad
Mode = ResizeMode.Pad,
PadColor = Color.Lavender
};
image.Mutate(x => x.Resize(options));

23
tests/ImageSharp.Tests/TestFormat.cs

@ -212,19 +212,16 @@ namespace SixLabors.ImageSharp.Tests
public int HeaderSize => this.testFormat.HeaderSize;
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
=> this.DecodeImpl<TPixel>(configuration, stream, default).GetAwaiter().GetResult();
=> this.DecodeImpl<TPixel>(configuration, stream);
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
=> this.DecodeImpl<TPixel>(configuration, stream, cancellationToken);
private async Task<Image<TPixel>> DecodeImpl<TPixel>(Configuration config, Stream stream, CancellationToken cancellationToken)
private Image<TPixel> DecodeImpl<TPixel>(Configuration config, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
var ms = new MemoryStream();
await stream.CopyToAsync(ms, config.StreamProcessingBufferSize, cancellationToken);
stream.CopyTo(ms, config.StreamProcessingBufferSize);
byte[] marker = ms.ToArray().Skip(this.testFormat.header.Length).ToArray();
this.testFormat.DecodeCalls.Add(new DecodeOperation
{
@ -239,16 +236,10 @@ namespace SixLabors.ImageSharp.Tests
public bool IsSupportedFileFormat(Span<byte> header) => this.testFormat.IsSupportedFileFormat(header);
public Image Decode(Configuration configuration, Stream stream) => this.Decode<TestPixelForAgnosticDecode>(configuration, stream);
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> await this.DecodeAsync<TestPixelForAgnosticDecode>(configuration, stream, cancellationToken);
public IImageInfo Identify(Configuration configuration, Stream stream) =>
this.IdentifyAsync(configuration, stream, default).GetAwaiter().GetResult();
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode<TestPixelForAgnosticDecode>(configuration, stream, cancellationToken);
public async Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> await this.DecodeImpl<Rgba32>(configuration, stream, cancellationToken);
public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken) =>
this.DecodeImpl<Rgba32>(configuration, stream);
}
public class TestEncoder : IImageEncoder

37
tests/ImageSharp.Tests/TestImages.cs

@ -262,6 +262,8 @@ namespace SixLabors.ImageSharp.Tests
public const string MalformedUnsupportedComponentCount = "Jpg/issues/issue-1900-malformed-unsupported-255-components.jpg";
public const string MultipleApp01932 = "Jpg/issues/issue-1932-app0-resolution.jpg";
public const string InvalidIptcTag = "Jpg/issues/Issue1942InvalidIptcTag.jpg";
public const string Issue2057App1Parsing = "Jpg/issues/Issue2057-App1Parsing.jpg";
public const string ExifNullArrayTag = "Jpg/issues/issue-2056-exif-null-array.jpg";
public static class Fuzz
{
@ -824,6 +826,9 @@ namespace SixLabors.ImageSharp.Tests
public const string Flower2BitPalette = "Tiff/flower-palette-02.tiff";
public const string Flower4BitPalette = "Tiff/flower-palette-04.tiff";
public const string Flower4BitPaletteGray = "Tiff/flower-minisblack-04.tiff";
public const string FLowerRgb3Bit = "Tiff/flower-rgb-3bit.tiff";
public const string FLowerRgb5Bit = "Tiff/flower-rgb-5bit.tiff";
public const string FLowerRgb6Bit = "Tiff/flower-rgb-6bit.tiff";
public const string Flower6BitGray = "Tiff/flower-minisblack-06.tiff";
public const string Flower8BitGray = "Tiff/flower-minisblack-08.tiff";
public const string Flower10BitGray = "Tiff/flower-minisblack-10.tiff";
@ -848,6 +853,38 @@ namespace SixLabors.ImageSharp.Tests
public const string Flower32BitGrayPredictorBigEndian = "Tiff/flower-minisblack-32_msb_deflate_predictor.tiff";
public const string Flower32BitGrayPredictorLittleEndian = "Tiff/flower-minisblack-32_lsb_deflate_predictor.tiff";
// Images with alpha channel.
public const string Rgba2BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha2bit.tiff";
public const string Rgba3BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha3bit.tiff";
public const string Rgba4BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha4bit.tiff";
public const string Rgba5BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha5bit.tiff";
public const string Rgba6BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha6bit.tiff";
public const string Rgba8BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha8bit.tiff";
public const string Rgba8BitUnassociatedAlphaWithPredictor = "Tiff/RgbaUnassociatedAlphaPredictor8bit.tiff";
public const string Rgba8BitPlanarUnassociatedAlpha = "Tiff/RgbaUnassociatedAlphaPlanar8bit.tiff";
public const string Rgba10BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha10bit_msb.tiff";
public const string Rgba10BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha10bit_lsb.tiff";
public const string Rgba12BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha12bit_msb.tiff";
public const string Rgba12BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha12bit_lsb.tiff";
public const string Rgba14BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha14bit_msb.tiff";
public const string Rgba14BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha14bit_lsb.tiff";
public const string Rgba16BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha16bit_msb.tiff";
public const string Rgba16BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha16bit_lsb.tiff";
public const string Rgba16BitUnassociatedAlphaBigEndianWithPredictor = "Tiff/RgbaUnassociatedAlphaPredictor16bit_msb.tiff";
public const string Rgba16BitUnassociatedAlphaLittleEndianWithPredictor = "Tiff/RgbaUnassociatedAlphaPredictor16bit_lsb.tiff";
public const string Rgba16BitPlanarUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlphaPlanar16bit_msb.tiff";
public const string Rgba16BitPlanarUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlphaPlanar16bit_lsb.tiff";
public const string Rgba24BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha24bit_msb.tiff";
public const string Rgba24BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha24bit_lsb.tiff";
public const string Rgba24BitPlanarUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlphaPlanar24bit_msb.tiff";
public const string Rgba24BitPlanarUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlphaPlanar24bit_lsb.tiff";
public const string Rgba32BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha32bit_msb.tiff";
public const string Rgba32BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha32bit_lsb.tiff";
public const string Rgba32BitUnassociatedAlphaBigEndianWithPredictor = "Tiff/RgbaUnassociatedAlphaPredictor32bit_msb.tiff";
public const string Rgba32BitUnassociatedAlphaLittleEndianWithPredictor = "Tiff/RgbaUnassociatedAlphaPredictor32bit_lsb.tiff";
public const string Rgba32BitPlanarUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlphaPlanar32bit_msb.tiff";
public const string Rgba32BitPlanarUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlphaPlanar32bit_lsb.tiff";
public const string Issues1716Rgb161616BitLittleEndian = "Tiff/Issues/Issue1716.tiff";
public const string Issues1891 = "Tiff/Issues/Issue1891.tiff";

16
tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs

@ -6,7 +6,6 @@ using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using ImageMagick;
using ImageMagick.Formats;
using SixLabors.ImageSharp.Formats;
@ -59,11 +58,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs
}
}
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel<TPixel>
=> Task.FromResult(this.Decode<TPixel>(configuration, stream));
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel<TPixel>
{
var bmpReadDefines = new BmpReadDefines
@ -84,13 +79,13 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs
MemoryGroup<TPixel> framePixels = frame.PixelBuffer.FastMemoryGroup;
using IUnsafePixelCollection<ushort> pixels = magicFrame.GetPixelsUnsafe();
if (magicFrame.Depth == 8 || magicFrame.Depth == 6 || magicFrame.Depth == 4 || magicFrame.Depth == 2 || magicFrame.Depth == 1 || magicFrame.Depth == 10 || magicFrame.Depth == 12)
if (magicFrame.Depth is 12 or 10 or 8 or 6 or 5 or 4 or 3 or 2 or 1)
{
byte[] data = pixels.ToByteArray(PixelMapping.RGBA);
FromRgba32Bytes(configuration, data, framePixels);
}
else if (magicFrame.Depth == 16 || magicFrame.Depth == 14)
else if (magicFrame.Depth is 16 or 14)
{
ushort[] data = pixels.ToShortArray(PixelMapping.RGBA);
Span<byte> bytes = MemoryMarshal.Cast<ushort, byte>(data.AsSpan());
@ -105,9 +100,6 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs
return new Image<TPixel>(configuration, new ImageMetadata(), framesList);
}
public Image Decode(Configuration configuration, Stream stream) => this.Decode<Rgba32>(configuration, stream);
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken);
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode<Rgba32>(configuration, stream, cancellationToken);
}
}

18
tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs

@ -3,8 +3,6 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.ColorSpaces.Conversion;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
@ -15,11 +13,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs
{
public static SystemDrawingReferenceDecoder Instance { get; } = new SystemDrawingReferenceDecoder();
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
=> Task.FromResult(this.Decode<TPixel>(configuration, stream));
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
using (var sourceBitmap = new System.Drawing.Bitmap(stream))
@ -49,10 +43,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs
}
}
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> Task.FromResult(this.Identify(configuration, stream));
public IImageInfo Identify(Configuration configuration, Stream stream)
public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
using (var sourceBitmap = new System.Drawing.Bitmap(stream))
{
@ -61,9 +52,6 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs
}
}
public Image Decode(Configuration configuration, Stream stream) => this.Decode<Rgba32>(configuration, stream);
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken);
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode<Rgba32>(configuration, stream, cancellationToken);
}
}

18
tests/ImageSharp.Tests/TestUtilities/TestPixel.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
using Xunit.Abstractions;
@ -16,6 +17,11 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities
public TestPixel(float red, float green, float blue, float alpha)
{
Guard.MustBeBetweenOrEqualTo(red, 0F, 1F, nameof(red));
Guard.MustBeBetweenOrEqualTo(green, 0F, 1F, nameof(green));
Guard.MustBeBetweenOrEqualTo(blue, 0F, 1F, nameof(blue));
Guard.MustBeBetweenOrEqualTo(alpha, 0F, 1F, nameof(alpha));
this.Red = red;
this.Green = green;
this.Blue = blue;
@ -33,14 +39,11 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities
public TPixel AsPixel()
{
var pix = default(TPixel);
pix.FromVector4(new System.Numerics.Vector4(this.Red, this.Green, this.Blue, this.Alpha));
pix.FromScaledVector4(new Vector4(this.Red, this.Green, this.Blue, this.Alpha));
return pix;
}
internal Span<TPixel> AsSpan()
{
return new Span<TPixel>(new[] { this.AsPixel() });
}
internal Span<TPixel> AsSpan() => new(new[] { this.AsPixel() });
public void Deserialize(IXunitSerializationInfo info)
{
@ -58,9 +61,6 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities
info.AddValue("alpha", this.Alpha);
}
public override string ToString()
{
return $"{typeof(TPixel).Name}{this.AsPixel().ToString()}";
}
public override string ToString() => $"{typeof(TPixel).Name}{this.AsPixel()}";
}
}

41
tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs

@ -5,7 +5,6 @@ using System;
using System.Collections.Concurrent;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Memory;
@ -350,9 +349,6 @@ namespace SixLabors.ImageSharp.Tests
private static readonly ConcurrentDictionary<string, int> InvocationCounts =
new ConcurrentDictionary<string, int>();
private static readonly ConcurrentDictionary<string, int> InvocationCountsAsync =
new ConcurrentDictionary<string, int>();
private static readonly object Monitor = new object();
private string callerName;
@ -365,35 +361,22 @@ namespace SixLabors.ImageSharp.Tests
}
}
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
InvocationCounts[this.callerName]++;
return new Image<TPixel>(42, 42);
}
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
InvocationCountsAsync[this.callerName]++;
return Task.FromResult(new Image<TPixel>(42, 42));
}
internal static int GetInvocationCount(string callerName) => InvocationCounts[callerName];
internal static int GetInvocationCountAsync(string callerName) => InvocationCountsAsync[callerName];
internal void InitCaller(string name)
{
this.callerName = name;
InvocationCounts[name] = 0;
InvocationCountsAsync[name] = 0;
}
public Image Decode(Configuration configuration, Stream stream) => this.Decode<Rgba32>(configuration, stream);
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken);
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode<Rgba32>(configuration, stream, cancellationToken);
}
private class TestDecoderWithParameters : IImageDecoder
@ -401,9 +384,6 @@ namespace SixLabors.ImageSharp.Tests
private static readonly ConcurrentDictionary<string, int> InvocationCounts =
new ConcurrentDictionary<string, int>();
private static readonly ConcurrentDictionary<string, int> InvocationCountsAsync =
new ConcurrentDictionary<string, int>();
private static readonly object Monitor = new object();
private string callerName;
@ -420,35 +400,22 @@ namespace SixLabors.ImageSharp.Tests
}
}
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
InvocationCounts[this.callerName]++;
return new Image<TPixel>(42, 42);
}
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
InvocationCountsAsync[this.callerName]++;
return Task.FromResult(new Image<TPixel>(42, 42));
}
internal static int GetInvocationCount(string callerName) => InvocationCounts[callerName];
internal static int GetInvocationCountAsync(string callerName) => InvocationCountsAsync[callerName];
internal void InitCaller(string name)
{
this.callerName = name;
InvocationCounts[name] = 0;
InvocationCountsAsync[name] = 0;
}
public Image Decode(Configuration configuration, Stream stream) => this.Decode<Rgba32>(configuration, stream);
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken);
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode<Rgba32>(configuration, stream, cancellationToken);
}
}
}

4
tests/Images/External/ReferenceOutput/ResizeTests/ResizeWithBoxPadMode_CalliphoraPartial.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1642520a9d4491f55af5a83280423929529e3d528637538d148b9dc2ecdd6d2d
size 365839
oid sha256:77086032bab11b91c68bc1686179063edbadc9d453574e4f087b2bbd677b4c8e
size 402367

4
tests/Images/External/ReferenceOutput/ResizeTests/ResizeWithPadMode_CalliphoraPartial.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:060826324dcd4baa1df1b075707a9bd9527cf87c1d156e3beb3d8abb499bdcd5
size 361829
oid sha256:5c0653aa2b726574fbea4cc308c269ff5e534d38bb48c0e77470c11042a395fd
size 400267

3
tests/Images/Input/Jpg/issues/Issue2057-App1Parsing.jpg

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7048dee15946bf981e5b0d2481ffcb8a64684fddca07172275b13a05f01b6b63
size 1631109

3
tests/Images/Input/Jpg/issues/issue-2056-exif-null-array.jpg

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4c52500be37a8ea1ee1caeb78c79e44b02e10912df4f6db65313c6745573c8ee
size 250451

3
tests/Images/Input/Tiff/RgbaUnassociatedAlpha10bit_lsb.tiff

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7f78ef11e4044d13ea3bf699e33472a708df3a5cc817dc41edb4df184f127f2b
size 294278

3
tests/Images/Input/Tiff/RgbaUnassociatedAlpha10bit_msb.tiff

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d9c2d6f4e16677d9fdfb38cc2bfb7df05eedbb8dc0e3c26a6dba9b427c2c698a
size 294278

3
tests/Images/Input/Tiff/RgbaUnassociatedAlpha12bit_lsb.tiff

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:53e9ff25da2a2a7a613328cfaf33799df51fe150586fb8de52070e8cc8830d97
size 353078

3
tests/Images/Input/Tiff/RgbaUnassociatedAlpha12bit_msb.tiff

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:caff76e01bc39b7a295f01a11e3787a6487ac002af5586dd956166a9c91eb048
size 353078

3
tests/Images/Input/Tiff/RgbaUnassociatedAlpha14bit_lsb.tiff

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9193b6a194be970b2cfb26369fa487fd6ec2f1656af11df2e48f1d6b0971bbf8
size 411878

3
tests/Images/Input/Tiff/RgbaUnassociatedAlpha14bit_msb.tiff

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:888bc84af8dffc4565b215412a8a2bb56f0c78211a082b893d87595cd9f555c1
size 411878

3
tests/Images/Input/Tiff/RgbaUnassociatedAlpha16bit_lsb.tiff

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6eae92c012ad56c084929e0a2aff7c93091224d9f8ab7f52f71b845792d6b763
size 470678

3
tests/Images/Input/Tiff/RgbaUnassociatedAlpha16bit_msb.tiff

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cbab54f221956215266c35bfd26fdfb123e092e3836e2401b9f24e1c5b23516e
size 470678

3
tests/Images/Input/Tiff/RgbaUnassociatedAlpha24bit_lsb.tiff

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:fd9fa514619604275cede0b4747291db2f8e5ad02095565c891ace2b537d6336
size 705878

3
tests/Images/Input/Tiff/RgbaUnassociatedAlpha24bit_msb.tiff

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:915ca9bbda952fc9ac78b44be07dab603948d51fb1a274935905e73cfe5bb0b9
size 705878

3
tests/Images/Input/Tiff/RgbaUnassociatedAlpha2bit.tiff

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f650c49faed4fd19b5527a0771489110090948e4ed33daa53b42c1776e288d89
size 59078

3
tests/Images/Input/Tiff/RgbaUnassociatedAlpha32bit_lsb.tiff

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8cad4c5f42d77539ce1f67efa7e0ed1fa4f5dd32b3269e5862453d878c6b18d7
size 941078

3
tests/Images/Input/Tiff/RgbaUnassociatedAlpha32bit_msb.tiff

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8874322776b8620573c26a3c84b8c7c9bf0aeaa7d68a7fef009f8838d14dca5b
size 941078

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

Loading…
Cancel
Save